mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
feat: ability to disable monitoring cmds (#840)
This commit is contained in:
@@ -1,9 +1,16 @@
|
||||
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';
|
||||
|
||||
/// Base class for all command type enums
|
||||
abstract class CommandType {
|
||||
abstract class CommandType implements Enum {
|
||||
String get cmd;
|
||||
|
||||
/// Get command-specific separator
|
||||
String get separator;
|
||||
|
||||
/// Get command-specific divider (separator with echo and formatting)
|
||||
String get divider;
|
||||
}
|
||||
|
||||
/// Linux/Unix status commands
|
||||
@@ -83,6 +90,12 @@ 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);
|
||||
}
|
||||
|
||||
/// BSD/macOS status commands
|
||||
@@ -102,6 +115,12 @@ 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);
|
||||
}
|
||||
|
||||
/// Windows PowerShell status commands
|
||||
@@ -225,6 +244,12 @@ 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);
|
||||
}
|
||||
|
||||
/// Extensions for StatusCmdType
|
||||
@@ -240,10 +265,10 @@ extension StatusCmdTypeX on StatusCmdType {
|
||||
};
|
||||
}
|
||||
|
||||
/// Generic extension for Enum types
|
||||
extension EnumX on Enum {
|
||||
/// Find out the required segment from [segments]
|
||||
String find(List<String> segments) {
|
||||
return segments[index];
|
||||
/// Extension for CommandType to find content in parsed map
|
||||
extension CommandTypeX on CommandType {
|
||||
/// Find the command output from the parsed script output map
|
||||
String findInMap(Map<String, String> parsedOutput) {
|
||||
return parsedOutput[name] ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@ 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 {
|
||||
sealed class ScriptBuilder {
|
||||
const ScriptBuilder();
|
||||
|
||||
/// Generate a complete script for all shell functions
|
||||
String buildScript(Map<String, String>? customCmds);
|
||||
String buildScript(Map<String, String>? customCmds, [List<String>? disabledCmdTypes]);
|
||||
|
||||
/// Get the script file name for this platform
|
||||
String get scriptFileName;
|
||||
@@ -23,9 +23,6 @@ abstract class ScriptBuilder {
|
||||
|
||||
/// 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
|
||||
@@ -53,13 +50,19 @@ class WindowsScriptBuilder extends ScriptBuilder {
|
||||
@override
|
||||
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')}';
|
||||
final sb = StringBuffer();
|
||||
for (final e in customCmds.entries) {
|
||||
final cmdDivider = ScriptConstants.getCustomCmdSeparator(e.key);
|
||||
sb.writeln(' Write-Host "$cmdDivider"');
|
||||
sb.writeln(' ${e.value}');
|
||||
}
|
||||
return '\n$sb';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String buildScript(Map<String, String>? customCmds) {
|
||||
String buildScript(Map<String, String>? customCmds, [List<String>? disabledCmdTypes]) {
|
||||
final sb = StringBuffer();
|
||||
sb.write(scriptHeader);
|
||||
|
||||
@@ -69,7 +72,7 @@ class WindowsScriptBuilder extends ScriptBuilder {
|
||||
|
||||
sb.write('''
|
||||
function ${func.name} {
|
||||
${_getWindowsCommand(func).split('\n').map((e) => e.isEmpty ? '' : ' $e').join('\n')}$customCmdsStr
|
||||
${_getWindowsCommand(func, disabledCmdTypes).split('\n').map((e) => e.isEmpty ? '' : ' $e').join('\n')}$customCmdsStr
|
||||
}
|
||||
|
||||
''');
|
||||
@@ -92,14 +95,20 @@ switch (\$args[0]) {
|
||||
}
|
||||
|
||||
/// Get Windows-specific command for a shell function
|
||||
String _getWindowsCommand(ShellFunc func) => switch (func) {
|
||||
ShellFunc.status => WindowsStatusCmdType.values.map((e) => e.cmd).join(cmdDivider),
|
||||
String _getWindowsCommand(ShellFunc func, [List<String>? disabledCmdTypes]) => switch (func) {
|
||||
ShellFunc.status => _getWindowsStatusCommand(disabledCmdTypes: disabledCmdTypes ?? []),
|
||||
ShellFunc.process => 'Get-Process | Select-Object ProcessName, Id, CPU, WorkingSet | ConvertTo-Json',
|
||||
ShellFunc.shutdown => 'Stop-Computer -Force',
|
||||
ShellFunc.reboot => 'Restart-Computer -Force',
|
||||
ShellFunc.suspend =>
|
||||
'Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.Application]::SetSuspendState(\'Suspend\', \$false, \$false)',
|
||||
};
|
||||
|
||||
/// Get Windows status command with command-specific separators
|
||||
String _getWindowsStatusCommand({required List<String> disabledCmdTypes}) {
|
||||
final cmdTypes = WindowsStatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.name));
|
||||
return cmdTypes.map((e) => '${e.divider}${e.cmd}').join('').trimRight(); // Remove trailing divider
|
||||
}
|
||||
}
|
||||
|
||||
/// Unix shell script builder
|
||||
@@ -129,13 +138,19 @@ chmod 755 $scriptPath
|
||||
@override
|
||||
String getCustomCmdsString(ShellFunc func, Map<String, String>? customCmds) {
|
||||
if (func == ShellFunc.status && customCmds != null && customCmds.isNotEmpty) {
|
||||
return '$cmdDivider\n\t${customCmds.values.join(cmdDivider)}';
|
||||
final sb = StringBuffer();
|
||||
for (final e in customCmds.entries) {
|
||||
final cmdDivider = ScriptConstants.getCustomCmdSeparator(e.key);
|
||||
sb.writeln('echo "$cmdDivider"');
|
||||
sb.writeln(e.value);
|
||||
}
|
||||
return '\n$sb';
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String buildScript(Map<String, String>? customCmds) {
|
||||
String buildScript(Map<String, String>? customCmds, [List<String>? disabledCmdTypes]) {
|
||||
final sb = StringBuffer();
|
||||
sb.write(scriptHeader);
|
||||
// Write each function
|
||||
@@ -143,7 +158,7 @@ chmod 755 $scriptPath
|
||||
final customCmdsStr = getCustomCmdsString(func, customCmds);
|
||||
sb.write('''
|
||||
${func.name}() {
|
||||
${_getUnixCommand(func).split('\n').map((e) => '\t$e').join('\n')}
|
||||
${_getUnixCommand(func, disabledCmdTypes).split('\n').map((e) => '\t$e').join('\n')}
|
||||
$customCmdsStr
|
||||
}
|
||||
|
||||
@@ -168,27 +183,24 @@ esac''');
|
||||
}
|
||||
|
||||
/// Get Unix-specific command for a shell function
|
||||
String _getUnixCommand(ShellFunc func) {
|
||||
switch (func) {
|
||||
case ShellFunc.status:
|
||||
return _getUnixStatusCommand();
|
||||
case ShellFunc.process:
|
||||
return _getUnixProcessCommand();
|
||||
case ShellFunc.shutdown:
|
||||
return _getUnixShutdownCommand();
|
||||
case ShellFunc.reboot:
|
||||
return _getUnixRebootCommand();
|
||||
case ShellFunc.suspend:
|
||||
return _getUnixSuspendCommand();
|
||||
}
|
||||
String _getUnixCommand(ShellFunc func, [List<String>? disabledCmdTypes]) {
|
||||
return switch (func) {
|
||||
ShellFunc.status => _getUnixStatusCommand(disabledCmdTypes: disabledCmdTypes ?? []),
|
||||
ShellFunc.process => _getUnixProcessCommand(),
|
||||
ShellFunc.shutdown => _getUnixShutdownCommand(),
|
||||
ShellFunc.reboot => _getUnixRebootCommand(),
|
||||
ShellFunc.suspend => _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);
|
||||
String _getUnixStatusCommand({required List<String> disabledCmdTypes}) {
|
||||
// Generate command lists with command-specific separators, filtering disabled commands
|
||||
final filteredLinuxCmdTypes = StatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.name));
|
||||
final linuxCommands = filteredLinuxCmdTypes.map((e) => '${e.divider}${e.cmd}').join('').trimRight();
|
||||
|
||||
final bsdCommands = BSDStatusCmdType.values.map((e) => e.cmd).join(cmdDivider);
|
||||
final filteredBsdCmdTypes = BSDStatusCmdType.values.where((e) => !disabledCmdTypes.contains(e.name));
|
||||
final bsdCommands = filteredBsdCmdTypes.map((e) => '${e.divider}${e.cmd}').join('').trimRight();
|
||||
|
||||
return '''
|
||||
if [ "\$macSign" = "" ] && [ "\$bsdSign" = "" ]; then
|
||||
|
||||
@@ -17,8 +17,58 @@ class ScriptConstants {
|
||||
// Command separators and dividers
|
||||
static const String separator = 'SrvBoxSep';
|
||||
|
||||
/// The suffix `\t` is for formatting
|
||||
static const String cmdDivider = '\necho $separator\n\t';
|
||||
/// Custom command separator
|
||||
static const String customCmdSep = 'SrvBoxCusCmdSep';
|
||||
|
||||
/// Generate command-specific separator
|
||||
static String getCmdSeparator(String cmdName) => '$separator.$cmdName';
|
||||
|
||||
/// Generate command-specific divider for custom commands
|
||||
static String getCustomCmdSeparator(String cmdName) => '$customCmdSep.$cmdName';
|
||||
|
||||
/// Generate command-specific divider
|
||||
static String getCmdDivider(String cmdName) => '\necho ${getCmdSeparator(cmdName)}\n\t';
|
||||
|
||||
/// Parse script output into command-specific map
|
||||
static Map<String, String> parseScriptOutput(String raw) {
|
||||
final result = <String, String>{};
|
||||
|
||||
if (raw.isEmpty) return result;
|
||||
|
||||
// Parse line by line to properly handle command-specific separators
|
||||
final lines = raw.split('\n');
|
||||
String? currentCmd;
|
||||
final buffer = StringBuffer();
|
||||
|
||||
for (final line in lines) {
|
||||
if (line.startsWith('$separator.')) {
|
||||
// Save previous command content
|
||||
if (currentCmd != null) {
|
||||
result[currentCmd] = buffer.toString().trim();
|
||||
buffer.clear();
|
||||
}
|
||||
// Start new command
|
||||
currentCmd = line.substring('$separator.'.length);
|
||||
} else if (line.startsWith('$customCmdSep.')) {
|
||||
// Save previous command content
|
||||
if (currentCmd != null) {
|
||||
result[currentCmd] = buffer.toString().trim();
|
||||
buffer.clear();
|
||||
}
|
||||
// Start new custom command
|
||||
currentCmd = line.substring('$customCmdSep.'.length);
|
||||
} else if (currentCmd != null) {
|
||||
buffer.writeln(line);
|
||||
}
|
||||
}
|
||||
|
||||
// Don't forget the last command
|
||||
if (currentCmd != null) {
|
||||
result[currentCmd] = buffer.toString().trim();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Path separators
|
||||
static const String unixPathSeparator = '/';
|
||||
|
||||
@@ -93,10 +93,10 @@ class ShellFuncManager {
|
||||
}
|
||||
|
||||
/// Generate complete script based on system type
|
||||
static String allScript(Map<String, String>? customCmds, {SystemType? systemType}) {
|
||||
static String allScript(Map<String, String>? customCmds, {SystemType? systemType, List<String>? disabledCmdTypes}) {
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
|
||||
return builder.buildScript(customCmds);
|
||||
return builder.buildScript(customCmds, disabledCmdTypes);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,6 +48,9 @@ abstract class Spi with _$Spi {
|
||||
|
||||
/// Custom system type (unix or windows). If set, skip auto-detection.
|
||||
@JsonKey(includeIfNull: false) SystemType? customSystemType,
|
||||
|
||||
/// Disabled command types for this server
|
||||
@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes,
|
||||
}) = _Spi;
|
||||
|
||||
factory Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
|
||||
|
||||
@@ -20,7 +20,8 @@ mixin _$Spi {
|
||||
@JsonKey(name: 'pubKeyId') String? get keyId; List<String>? get tags; String? get alterUrl; bool get autoConnect;/// [id] of the jump server
|
||||
String? get jumpId; ServerCustom? get custom; WakeOnLanCfg? get wolCfg;/// It only applies to SSH terminal.
|
||||
Map<String, String>? get envs;@JsonKey(fromJson: Spi.parseId) String get id;/// Custom system type (unix or windows). If set, skip auto-detection.
|
||||
@JsonKey(includeIfNull: false) SystemType? get customSystemType;
|
||||
@JsonKey(includeIfNull: false) SystemType? get customSystemType;/// Disabled command types for this server
|
||||
@JsonKey(includeIfNull: false) List<String>? get disabledCmdTypes;
|
||||
/// Create a copy of Spi
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@@ -33,12 +34,12 @@ $SpiCopyWith<Spi> get copyWith => _$SpiCopyWithImpl<Spi>(this as Spi, _$identity
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other.envs, envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other.tags, tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other.envs, envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other.disabledCmdTypes, disabledCmdTypes));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(envs),id,customSystemType);
|
||||
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(envs),id,customSystemType,const DeepCollectionEquality().hash(disabledCmdTypes));
|
||||
|
||||
|
||||
|
||||
@@ -49,7 +50,7 @@ abstract mixin class $SpiCopyWith<$Res> {
|
||||
factory $SpiCopyWith(Spi value, $Res Function(Spi) _then) = _$SpiCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType
|
||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes
|
||||
});
|
||||
|
||||
|
||||
@@ -66,7 +67,7 @@ class _$SpiCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of Spi
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,}) {
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||
@@ -83,7 +84,8 @@ as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: ca
|
||||
as WakeOnLanCfg?,envs: freezed == envs ? _self.envs : envs // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,customSystemType: freezed == customSystemType ? _self.customSystemType : customSystemType // ignore: cast_nullable_to_non_nullable
|
||||
as SystemType?,
|
||||
as SystemType?,disabledCmdTypes: freezed == disabledCmdTypes ? _self.disabledCmdTypes : disabledCmdTypes // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
));
|
||||
}
|
||||
|
||||
@@ -94,7 +96,7 @@ as SystemType?,
|
||||
|
||||
@JsonSerializable(includeIfNull: false)
|
||||
class _Spi extends Spi {
|
||||
const _Spi({required this.name, required this.ip, required this.port, required this.user, this.pwd, @JsonKey(name: 'pubKeyId') this.keyId, final List<String>? tags, this.alterUrl, this.autoConnect = true, this.jumpId, this.custom, this.wolCfg, final Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) this.id = '', @JsonKey(includeIfNull: false) this.customSystemType}): _tags = tags,_envs = envs,super._();
|
||||
const _Spi({required this.name, required this.ip, required this.port, required this.user, this.pwd, @JsonKey(name: 'pubKeyId') this.keyId, final List<String>? tags, this.alterUrl, this.autoConnect = true, this.jumpId, this.custom, this.wolCfg, final Map<String, String>? envs, @JsonKey(fromJson: Spi.parseId) this.id = '', @JsonKey(includeIfNull: false) this.customSystemType, @JsonKey(includeIfNull: false) final List<String>? disabledCmdTypes}): _tags = tags,_envs = envs,_disabledCmdTypes = disabledCmdTypes,super._();
|
||||
factory _Spi.fromJson(Map<String, dynamic> json) => _$SpiFromJson(json);
|
||||
|
||||
@override final String name;
|
||||
@@ -133,6 +135,17 @@ class _Spi extends Spi {
|
||||
@override@JsonKey(fromJson: Spi.parseId) final String id;
|
||||
/// Custom system type (unix or windows). If set, skip auto-detection.
|
||||
@override@JsonKey(includeIfNull: false) final SystemType? customSystemType;
|
||||
/// Disabled command types for this server
|
||||
final List<String>? _disabledCmdTypes;
|
||||
/// Disabled command types for this server
|
||||
@override@JsonKey(includeIfNull: false) List<String>? get disabledCmdTypes {
|
||||
final value = _disabledCmdTypes;
|
||||
if (value == null) return null;
|
||||
if (_disabledCmdTypes is EqualUnmodifiableListView) return _disabledCmdTypes;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of Spi
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -147,12 +160,12 @@ Map<String, dynamic> toJson() {
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other._envs, _envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType));
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _Spi&&(identical(other.name, name) || other.name == name)&&(identical(other.ip, ip) || other.ip == ip)&&(identical(other.port, port) || other.port == port)&&(identical(other.user, user) || other.user == user)&&(identical(other.pwd, pwd) || other.pwd == pwd)&&(identical(other.keyId, keyId) || other.keyId == keyId)&&const DeepCollectionEquality().equals(other._tags, _tags)&&(identical(other.alterUrl, alterUrl) || other.alterUrl == alterUrl)&&(identical(other.autoConnect, autoConnect) || other.autoConnect == autoConnect)&&(identical(other.jumpId, jumpId) || other.jumpId == jumpId)&&(identical(other.custom, custom) || other.custom == custom)&&(identical(other.wolCfg, wolCfg) || other.wolCfg == wolCfg)&&const DeepCollectionEquality().equals(other._envs, _envs)&&(identical(other.id, id) || other.id == id)&&(identical(other.customSystemType, customSystemType) || other.customSystemType == customSystemType)&&const DeepCollectionEquality().equals(other._disabledCmdTypes, _disabledCmdTypes));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(_tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(_envs),id,customSystemType);
|
||||
int get hashCode => Object.hash(runtimeType,name,ip,port,user,pwd,keyId,const DeepCollectionEquality().hash(_tags),alterUrl,autoConnect,jumpId,custom,wolCfg,const DeepCollectionEquality().hash(_envs),id,customSystemType,const DeepCollectionEquality().hash(_disabledCmdTypes));
|
||||
|
||||
|
||||
|
||||
@@ -163,7 +176,7 @@ abstract mixin class _$SpiCopyWith<$Res> implements $SpiCopyWith<$Res> {
|
||||
factory _$SpiCopyWith(_Spi value, $Res Function(_Spi) _then) = __$SpiCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType
|
||||
String name, String ip, int port, String user, String? pwd,@JsonKey(name: 'pubKeyId') String? keyId, List<String>? tags, String? alterUrl, bool autoConnect, String? jumpId, ServerCustom? custom, WakeOnLanCfg? wolCfg, Map<String, String>? envs,@JsonKey(fromJson: Spi.parseId) String id,@JsonKey(includeIfNull: false) SystemType? customSystemType,@JsonKey(includeIfNull: false) List<String>? disabledCmdTypes
|
||||
});
|
||||
|
||||
|
||||
@@ -180,7 +193,7 @@ class __$SpiCopyWithImpl<$Res>
|
||||
|
||||
/// Create a copy of Spi
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,}) {
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? name = null,Object? ip = null,Object? port = null,Object? user = null,Object? pwd = freezed,Object? keyId = freezed,Object? tags = freezed,Object? alterUrl = freezed,Object? autoConnect = null,Object? jumpId = freezed,Object? custom = freezed,Object? wolCfg = freezed,Object? envs = freezed,Object? id = null,Object? customSystemType = freezed,Object? disabledCmdTypes = freezed,}) {
|
||||
return _then(_Spi(
|
||||
name: null == name ? _self.name : name // ignore: cast_nullable_to_non_nullable
|
||||
as String,ip: null == ip ? _self.ip : ip // ignore: cast_nullable_to_non_nullable
|
||||
@@ -197,7 +210,8 @@ as ServerCustom?,wolCfg: freezed == wolCfg ? _self.wolCfg : wolCfg // ignore: ca
|
||||
as WakeOnLanCfg?,envs: freezed == envs ? _self._envs : envs // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, String>?,id: null == id ? _self.id : id // ignore: cast_nullable_to_non_nullable
|
||||
as String,customSystemType: freezed == customSystemType ? _self.customSystemType : customSystemType // ignore: cast_nullable_to_non_nullable
|
||||
as SystemType?,
|
||||
as SystemType?,disabledCmdTypes: freezed == disabledCmdTypes ? _self._disabledCmdTypes : disabledCmdTypes // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
@@ -31,6 +31,9 @@ _Spi _$SpiFromJson(Map<String, dynamic> json) => _Spi(
|
||||
_$SystemTypeEnumMap,
|
||||
json['customSystemType'],
|
||||
),
|
||||
disabledCmdTypes: (json['disabledCmdTypes'] as List<dynamic>?)
|
||||
?.map((e) => e as String)
|
||||
.toList(),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$SpiToJson(_Spi instance) => <String, dynamic>{
|
||||
@@ -50,6 +53,7 @@ Map<String, dynamic> _$SpiToJson(_Spi instance) => <String, dynamic>{
|
||||
'id': instance.id,
|
||||
if (_$SystemTypeEnumMap[instance.customSystemType] case final value?)
|
||||
'customSystemType': value,
|
||||
if (instance.disabledCmdTypes case final value?) 'disabledCmdTypes': value,
|
||||
};
|
||||
|
||||
const _$SystemTypeEnumMap = {
|
||||
|
||||
@@ -20,14 +20,14 @@ import 'package:server_box/data/model/server/windows_parser.dart';
|
||||
|
||||
class ServerStatusUpdateReq {
|
||||
final ServerStatus ss;
|
||||
final List<String> segments;
|
||||
final Map<String, String> parsedOutput;
|
||||
final SystemType system;
|
||||
final Map<String, String> customCmds;
|
||||
|
||||
const ServerStatusUpdateReq({
|
||||
required this.system,
|
||||
required this.ss,
|
||||
required this.segments,
|
||||
required this.parsedOutput,
|
||||
required this.customCmds,
|
||||
});
|
||||
}
|
||||
@@ -43,20 +43,20 @@ Future<ServerStatus> getStatus(ServerStatusUpdateReq req) async {
|
||||
// Wrap each operation with a try-catch, so that if one operation fails,
|
||||
// the following operations can still be executed.
|
||||
Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
final segments = req.segments;
|
||||
final parsedOutput = req.parsedOutput;
|
||||
|
||||
final time = int.tryParse(StatusCmdType.time.find(segments)) ??
|
||||
final time = int.tryParse(StatusCmdType.time.findInMap(parsedOutput)) ??
|
||||
DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
try {
|
||||
final net = NetSpeed.parse(StatusCmdType.net.find(segments), time);
|
||||
final net = NetSpeed.parse(StatusCmdType.net.findInMap(parsedOutput), time);
|
||||
req.ss.netSpeed.update(net);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
final sys = _parseSysVer(StatusCmdType.sys.find(segments));
|
||||
final sys = _parseSysVer(StatusCmdType.sys.findInMap(parsedOutput));
|
||||
if (sys != null) {
|
||||
req.ss.more[StatusCmdType.sys] = sys;
|
||||
}
|
||||
@@ -65,7 +65,7 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
}
|
||||
|
||||
try {
|
||||
final host = _parseHostName(StatusCmdType.host.find(segments));
|
||||
final host = _parseHostName(StatusCmdType.host.findInMap(parsedOutput));
|
||||
if (host != null) {
|
||||
req.ss.more[StatusCmdType.host] = host;
|
||||
}
|
||||
@@ -74,9 +74,9 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
}
|
||||
|
||||
try {
|
||||
final cpus = SingleCpuCore.parse(StatusCmdType.cpu.find(segments));
|
||||
final cpus = SingleCpuCore.parse(StatusCmdType.cpu.findInMap(parsedOutput));
|
||||
req.ss.cpu.update(cpus);
|
||||
final brand = CpuBrand.parse(StatusCmdType.cpuBrand.find(segments));
|
||||
final brand = CpuBrand.parse(StatusCmdType.cpuBrand.findInMap(parsedOutput));
|
||||
req.ss.cpu.brand.clear();
|
||||
req.ss.cpu.brand.addAll(brand);
|
||||
} catch (e, s) {
|
||||
@@ -85,15 +85,15 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
|
||||
try {
|
||||
req.ss.temps.parse(
|
||||
StatusCmdType.tempType.find(segments),
|
||||
StatusCmdType.tempVal.find(segments),
|
||||
StatusCmdType.tempType.findInMap(parsedOutput),
|
||||
StatusCmdType.tempVal.findInMap(parsedOutput),
|
||||
);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
final tcp = Conn.parse(StatusCmdType.conn.find(segments));
|
||||
final tcp = Conn.parse(StatusCmdType.conn.findInMap(parsedOutput));
|
||||
if (tcp != null) {
|
||||
req.ss.tcp = tcp;
|
||||
}
|
||||
@@ -102,20 +102,20 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.disk = Disk.parse(StatusCmdType.disk.find(segments));
|
||||
req.ss.disk = Disk.parse(StatusCmdType.disk.findInMap(parsedOutput));
|
||||
req.ss.diskUsage = DiskUsage.parse(req.ss.disk);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.mem = Memory.parse(StatusCmdType.mem.find(segments));
|
||||
req.ss.mem = Memory.parse(StatusCmdType.mem.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
final uptime = _parseUpTime(StatusCmdType.uptime.find(segments));
|
||||
final uptime = _parseUpTime(StatusCmdType.uptime.findInMap(parsedOutput));
|
||||
if (uptime != null) {
|
||||
req.ss.more[StatusCmdType.uptime] = uptime;
|
||||
}
|
||||
@@ -124,39 +124,39 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.swap = Swap.parse(StatusCmdType.mem.find(segments));
|
||||
req.ss.swap = Swap.parse(StatusCmdType.mem.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
final diskio = DiskIO.parse(StatusCmdType.diskio.find(segments), time);
|
||||
final diskio = DiskIO.parse(StatusCmdType.diskio.findInMap(parsedOutput), time);
|
||||
req.ss.diskIO.update(diskio);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
final smarts = DiskSmart.parse(StatusCmdType.diskSmart.find(segments));
|
||||
final smarts = DiskSmart.parse(StatusCmdType.diskSmart.findInMap(parsedOutput));
|
||||
req.ss.diskSmart = smarts;
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.nvidia = NvidiaSmi.fromXml(StatusCmdType.nvidia.find(segments));
|
||||
req.ss.nvidia = NvidiaSmi.fromXml(StatusCmdType.nvidia.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.amd = AmdSmi.fromJson(StatusCmdType.amd.find(segments));
|
||||
req.ss.amd = AmdSmi.fromJson(StatusCmdType.amd.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
final battery = StatusCmdType.battery.find(segments);
|
||||
final battery = StatusCmdType.battery.findInMap(parsedOutput);
|
||||
|
||||
/// Only collect li-poly batteries
|
||||
final batteries = Batteries.parse(battery, true);
|
||||
@@ -169,7 +169,7 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
}
|
||||
|
||||
try {
|
||||
final sensors = SensorItem.parse(StatusCmdType.sensors.find(segments));
|
||||
final sensors = SensorItem.parse(StatusCmdType.sensors.findInMap(parsedOutput));
|
||||
if (sensors.isNotEmpty) {
|
||||
req.ss.sensors.clear();
|
||||
req.ss.sensors.addAll(sensors);
|
||||
@@ -179,9 +179,9 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
}
|
||||
|
||||
try {
|
||||
for (int idx = 0; idx < req.customCmds.length; idx++) {
|
||||
final key = req.customCmds.keys.elementAt(idx);
|
||||
final value = req.segments[idx + req.system.segmentsLen];
|
||||
for (final entry in req.customCmds.entries) {
|
||||
final key = entry.key;
|
||||
final value = req.parsedOutput[key] ?? '';
|
||||
req.ss.customCmds[key] = value;
|
||||
}
|
||||
} catch (e, s) {
|
||||
@@ -193,36 +193,36 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
|
||||
|
||||
// Same as above, wrap with try-catch
|
||||
Future<ServerStatus> _getBsdStatus(ServerStatusUpdateReq req) async {
|
||||
final segments = req.segments;
|
||||
final parsedOutput = req.parsedOutput;
|
||||
|
||||
try {
|
||||
final time = int.parse(BSDStatusCmdType.time.find(segments));
|
||||
final net = NetSpeed.parseBsd(BSDStatusCmdType.net.find(segments), time);
|
||||
final time = int.parse(BSDStatusCmdType.time.findInMap(parsedOutput));
|
||||
final net = NetSpeed.parseBsd(BSDStatusCmdType.net.findInMap(parsedOutput), time);
|
||||
req.ss.netSpeed.update(net);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.more[StatusCmdType.sys] = BSDStatusCmdType.sys.find(segments);
|
||||
req.ss.more[StatusCmdType.sys] = BSDStatusCmdType.sys.findInMap(parsedOutput);
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.cpu = parseBsdCpu(BSDStatusCmdType.cpu.find(segments));
|
||||
req.ss.cpu = parseBsdCpu(BSDStatusCmdType.cpu.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.mem = parseBsdMemory(BSDStatusCmdType.mem.find(segments));
|
||||
req.ss.mem = parseBsdMemory(BSDStatusCmdType.mem.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
|
||||
try {
|
||||
final uptime = _parseUpTime(BSDStatusCmdType.uptime.find(segments));
|
||||
final uptime = _parseUpTime(BSDStatusCmdType.uptime.findInMap(parsedOutput));
|
||||
if (uptime != null) {
|
||||
req.ss.more[StatusCmdType.uptime] = uptime;
|
||||
}
|
||||
@@ -231,7 +231,7 @@ Future<ServerStatus> _getBsdStatus(ServerStatusUpdateReq req) async {
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.disk = Disk.parse(BSDStatusCmdType.disk.find(segments));
|
||||
req.ss.disk = Disk.parse(BSDStatusCmdType.disk.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning(e, s);
|
||||
}
|
||||
@@ -302,32 +302,32 @@ String? _parseHostName(String raw) {
|
||||
|
||||
// Windows status parsing implementation
|
||||
Future<ServerStatus> _getWindowsStatus(ServerStatusUpdateReq req) async {
|
||||
final segments = req.segments;
|
||||
final time = int.tryParse(WindowsStatusCmdType.time.find(segments)) ??
|
||||
final parsedOutput = req.parsedOutput;
|
||||
final time = int.tryParse(WindowsStatusCmdType.time.findInMap(parsedOutput)) ??
|
||||
DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
|
||||
// Parse all different resource types using helper methods
|
||||
_parseWindowsNetworkData(req, segments, time);
|
||||
_parseWindowsSystemData(req, segments);
|
||||
_parseWindowsHostData(req, segments);
|
||||
_parseWindowsCpuData(req, segments);
|
||||
_parseWindowsMemoryData(req, segments);
|
||||
_parseWindowsDiskData(req, segments);
|
||||
_parseWindowsUptimeData(req, segments);
|
||||
_parseWindowsDiskIOData(req, segments, time);
|
||||
_parseWindowsConnectionData(req, segments);
|
||||
_parseWindowsBatteryData(req, segments);
|
||||
_parseWindowsTemperatureData(req, segments);
|
||||
_parseWindowsGpuData(req, segments);
|
||||
WindowsParser.parseCustomCommands(req.ss, segments, req.customCmds, req.system.segmentsLen);
|
||||
_parseWindowsNetworkData(req, parsedOutput, time);
|
||||
_parseWindowsSystemData(req, parsedOutput);
|
||||
_parseWindowsHostData(req, parsedOutput);
|
||||
_parseWindowsCpuData(req, parsedOutput);
|
||||
_parseWindowsMemoryData(req, parsedOutput);
|
||||
_parseWindowsDiskData(req, parsedOutput);
|
||||
_parseWindowsUptimeData(req, parsedOutput);
|
||||
_parseWindowsDiskIOData(req, parsedOutput, time);
|
||||
_parseWindowsConnectionData(req, parsedOutput);
|
||||
_parseWindowsBatteryData(req, parsedOutput);
|
||||
_parseWindowsTemperatureData(req, parsedOutput);
|
||||
_parseWindowsGpuData(req, parsedOutput);
|
||||
WindowsParser.parseCustomCommands(req.ss, req.parsedOutput, req.customCmds);
|
||||
|
||||
return req.ss;
|
||||
}
|
||||
|
||||
/// Parse Windows network data
|
||||
void _parseWindowsNetworkData(ServerStatusUpdateReq req, List<String> segments, int time) {
|
||||
void _parseWindowsNetworkData(ServerStatusUpdateReq req, Map<String, String> parsedOutput, int time) {
|
||||
try {
|
||||
final netRaw = WindowsStatusCmdType.net.find(segments);
|
||||
final netRaw = WindowsStatusCmdType.net.findInMap(parsedOutput);
|
||||
if (netRaw.isNotEmpty &&
|
||||
netRaw != 'null' &&
|
||||
!netRaw.contains('network_error') &&
|
||||
@@ -344,9 +344,9 @@ void _parseWindowsNetworkData(ServerStatusUpdateReq req, List<String> segments,
|
||||
}
|
||||
|
||||
/// Parse Windows system information
|
||||
void _parseWindowsSystemData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsSystemData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final sys = WindowsStatusCmdType.sys.find(segments);
|
||||
final sys = WindowsStatusCmdType.sys.findInMap(parsedOutput);
|
||||
if (sys.isNotEmpty) {
|
||||
req.ss.more[StatusCmdType.sys] = sys;
|
||||
}
|
||||
@@ -356,9 +356,9 @@ void _parseWindowsSystemData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
}
|
||||
|
||||
/// Parse Windows host information
|
||||
void _parseWindowsHostData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsHostData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final host = _parseHostName(WindowsStatusCmdType.host.find(segments));
|
||||
final host = _parseHostName(WindowsStatusCmdType.host.findInMap(parsedOutput));
|
||||
if (host != null) {
|
||||
req.ss.more[StatusCmdType.host] = host;
|
||||
}
|
||||
@@ -368,10 +368,10 @@ void _parseWindowsHostData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
}
|
||||
|
||||
/// Parse Windows CPU data and brand information
|
||||
void _parseWindowsCpuData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsCpuData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
// Windows CPU parsing - JSON format from PowerShell
|
||||
final cpuRaw = WindowsStatusCmdType.cpu.find(segments);
|
||||
final cpuRaw = WindowsStatusCmdType.cpu.findInMap(parsedOutput);
|
||||
if (cpuRaw.isNotEmpty &&
|
||||
cpuRaw != 'null' &&
|
||||
!cpuRaw.contains('error') &&
|
||||
@@ -383,7 +383,7 @@ void _parseWindowsCpuData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
}
|
||||
|
||||
// Windows CPU brand parsing
|
||||
final brandRaw = WindowsStatusCmdType.cpuBrand.find(segments);
|
||||
final brandRaw = WindowsStatusCmdType.cpuBrand.findInMap(parsedOutput);
|
||||
if (brandRaw.isNotEmpty && brandRaw != 'null') {
|
||||
req.ss.cpu.brand.clear();
|
||||
req.ss.cpu.brand[brandRaw.trim()] = 1;
|
||||
@@ -394,9 +394,9 @@ void _parseWindowsCpuData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
}
|
||||
|
||||
/// Parse Windows memory data
|
||||
void _parseWindowsMemoryData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsMemoryData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final memRaw = WindowsStatusCmdType.mem.find(segments);
|
||||
final memRaw = WindowsStatusCmdType.mem.findInMap(parsedOutput);
|
||||
if (memRaw.isNotEmpty &&
|
||||
memRaw != 'null' &&
|
||||
!memRaw.contains('error') &&
|
||||
@@ -412,9 +412,9 @@ void _parseWindowsMemoryData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
}
|
||||
|
||||
/// Parse Windows disk data
|
||||
void _parseWindowsDiskData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsDiskData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final diskRaw = WindowsStatusCmdType.disk.find(segments);
|
||||
final diskRaw = WindowsStatusCmdType.disk.findInMap(parsedOutput);
|
||||
if (diskRaw.isNotEmpty && diskRaw != 'null') {
|
||||
final disks = WindowsParser.parseDisks(diskRaw);
|
||||
req.ss.disk = disks;
|
||||
@@ -426,9 +426,9 @@ void _parseWindowsDiskData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
}
|
||||
|
||||
/// Parse Windows uptime data
|
||||
void _parseWindowsUptimeData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsUptimeData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final uptime = WindowsParser.parseUpTime(WindowsStatusCmdType.uptime.find(segments));
|
||||
final uptime = WindowsParser.parseUpTime(WindowsStatusCmdType.uptime.findInMap(parsedOutput));
|
||||
if (uptime != null) {
|
||||
req.ss.more[StatusCmdType.uptime] = uptime;
|
||||
}
|
||||
@@ -438,9 +438,9 @@ void _parseWindowsUptimeData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
}
|
||||
|
||||
/// Parse Windows disk I/O data
|
||||
void _parseWindowsDiskIOData(ServerStatusUpdateReq req, List<String> segments, int time) {
|
||||
void _parseWindowsDiskIOData(ServerStatusUpdateReq req, Map<String, String> parsedOutput, int time) {
|
||||
try {
|
||||
final diskIOraw = WindowsStatusCmdType.diskio.find(segments);
|
||||
final diskIOraw = WindowsStatusCmdType.diskio.findInMap(parsedOutput);
|
||||
if (diskIOraw.isNotEmpty && diskIOraw != 'null') {
|
||||
final diskio = _parseWindowsDiskIO(diskIOraw, time);
|
||||
req.ss.diskIO.update(diskio);
|
||||
@@ -451,9 +451,9 @@ void _parseWindowsDiskIOData(ServerStatusUpdateReq req, List<String> segments, i
|
||||
}
|
||||
|
||||
/// Parse Windows connection data
|
||||
void _parseWindowsConnectionData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsConnectionData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final connStr = WindowsStatusCmdType.conn.find(segments);
|
||||
final connStr = WindowsStatusCmdType.conn.findInMap(parsedOutput);
|
||||
final connCount = int.tryParse(connStr.trim());
|
||||
if (connCount != null) {
|
||||
req.ss.tcp = Conn(maxConn: 0, active: connCount, passive: 0, fail: 0);
|
||||
@@ -464,9 +464,9 @@ void _parseWindowsConnectionData(ServerStatusUpdateReq req, List<String> segment
|
||||
}
|
||||
|
||||
/// Parse Windows battery data
|
||||
void _parseWindowsBatteryData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsBatteryData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final batteryRaw = WindowsStatusCmdType.battery.find(segments);
|
||||
final batteryRaw = WindowsStatusCmdType.battery.findInMap(parsedOutput);
|
||||
if (batteryRaw.isNotEmpty && batteryRaw != 'null') {
|
||||
final batteries = _parseWindowsBatteries(batteryRaw);
|
||||
req.ss.batteries.clear();
|
||||
@@ -480,9 +480,9 @@ void _parseWindowsBatteryData(ServerStatusUpdateReq req, List<String> segments)
|
||||
}
|
||||
|
||||
/// Parse Windows temperature data
|
||||
void _parseWindowsTemperatureData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsTemperatureData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
final tempRaw = WindowsStatusCmdType.temp.find(segments);
|
||||
final tempRaw = WindowsStatusCmdType.temp.findInMap(parsedOutput);
|
||||
if (tempRaw.isNotEmpty && tempRaw != 'null') {
|
||||
_parseWindowsTemperatures(req.ss.temps, tempRaw);
|
||||
}
|
||||
@@ -492,15 +492,15 @@ void _parseWindowsTemperatureData(ServerStatusUpdateReq req, List<String> segmen
|
||||
}
|
||||
|
||||
/// Parse Windows GPU data (NVIDIA/AMD)
|
||||
void _parseWindowsGpuData(ServerStatusUpdateReq req, List<String> segments) {
|
||||
void _parseWindowsGpuData(ServerStatusUpdateReq req, Map<String, String> parsedOutput) {
|
||||
try {
|
||||
req.ss.nvidia = NvidiaSmi.fromXml(WindowsStatusCmdType.nvidia.find(segments));
|
||||
req.ss.nvidia = NvidiaSmi.fromXml(WindowsStatusCmdType.nvidia.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Windows NVIDIA GPU parsing failed: $e', s);
|
||||
}
|
||||
|
||||
try {
|
||||
req.ss.amd = AmdSmi.fromJson(WindowsStatusCmdType.amd.find(segments));
|
||||
req.ss.amd = AmdSmi.fromJson(WindowsStatusCmdType.amd.findInMap(parsedOutput));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Windows AMD GPU parsing failed: $e', s);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
|
||||
enum SystemType {
|
||||
linux(linuxSign),
|
||||
@@ -53,16 +52,4 @@ enum SystemType {
|
||||
return SystemType.linux;
|
||||
}
|
||||
|
||||
bool isSegmentsLenMatch(int len) => len == segmentsLen;
|
||||
|
||||
int get segmentsLen {
|
||||
switch (this) {
|
||||
case SystemType.linux:
|
||||
return StatusCmdType.values.length;
|
||||
case SystemType.bsd:
|
||||
return BSDStatusCmdType.values.length;
|
||||
case SystemType.windows:
|
||||
return WindowsStatusCmdType.values.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,27 +15,17 @@ import 'package:server_box/data/model/server/server.dart';
|
||||
class WindowsParser {
|
||||
const WindowsParser._();
|
||||
|
||||
/// Parse Windows custom commands from segments
|
||||
/// Parse Windows custom commands from parsed output
|
||||
static void parseCustomCommands(
|
||||
ServerStatus serverStatus,
|
||||
List<String> segments,
|
||||
Map<String, String> parsedOutput,
|
||||
Map<String, String> customCmds,
|
||||
int systemSegmentsLength,
|
||||
) {
|
||||
try {
|
||||
for (int idx = 0; idx < customCmds.length; idx++) {
|
||||
final key = customCmds.keys.elementAt(idx);
|
||||
// Ensure we don't go out of bounds when accessing segments
|
||||
final segmentIndex = idx + systemSegmentsLength;
|
||||
if (segmentIndex < segments.length) {
|
||||
final value = segments[segmentIndex];
|
||||
serverStatus.customCmds[key] = value;
|
||||
} else {
|
||||
Loggers.app.warning(
|
||||
'Windows custom commands: segment index $segmentIndex out of bounds '
|
||||
'(segments length: ${segments.length}, systemSegmentsLength: $systemSegmentsLength)'
|
||||
);
|
||||
}
|
||||
for (final entry in customCmds.entries) {
|
||||
final key = entry.key;
|
||||
final value = parsedOutput[key] ?? '';
|
||||
serverStatus.customCmds[key] = value;
|
||||
}
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('Windows custom commands parsing failed: $e', s);
|
||||
|
||||
@@ -338,7 +338,7 @@ class ServerProvider extends Provider {
|
||||
sv.status.system = detectedSystemType;
|
||||
|
||||
final (_, writeScriptResult) = await sv.client!.exec((session) async {
|
||||
final scriptRaw = ShellFuncManager.allScript(spi.custom?.cmds, systemType: detectedSystemType).uint8List;
|
||||
final scriptRaw = ShellFuncManager.allScript(spi.custom?.cmds, systemType: detectedSystemType, disabledCmdTypes: spi.disabledCmdTypes).uint8List;
|
||||
session.stdin.add(scriptRaw);
|
||||
session.stdin.close();
|
||||
}, entry: ShellFuncManager.getInstallShellCmd(spi.id, systemType: detectedSystemType));
|
||||
@@ -406,31 +406,14 @@ class ServerProvider extends Provider {
|
||||
return;
|
||||
}
|
||||
|
||||
final systemType = SystemType.parse(segments[0]);
|
||||
final customCmdLen = spi.custom?.cmds?.length ?? 0;
|
||||
if (!systemType.isSegmentsLenMatch(segments.length - customCmdLen)) {
|
||||
TryLimiter.inc(sid);
|
||||
if (raw.contains('Could not chdir to home directory /var/services/')) {
|
||||
sv.status.err = SSHErr(type: SSHErrType.chdir, message: raw);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
return;
|
||||
}
|
||||
final expected = systemType.segmentsLen;
|
||||
final actual = segments.length;
|
||||
sv.status.err = SSHErr(
|
||||
type: SSHErrType.segements,
|
||||
message: 'Segments: expect $expected, got $actual, raw:\n\n$raw',
|
||||
);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
return;
|
||||
}
|
||||
sv.status.system = systemType;
|
||||
|
||||
try {
|
||||
// Parse script output into command-specific map
|
||||
final parsedOutput = ScriptConstants.parseScriptOutput(raw);
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
ss: sv.status,
|
||||
segments: segments,
|
||||
system: systemType,
|
||||
parsedOutput: parsedOutput,
|
||||
system: sv.status.system,
|
||||
customCmds: spi.custom?.cmds ?? {},
|
||||
);
|
||||
sv.status = await Computer.shared.start(getStatus, req, taskName: 'StatusUpdateReq<${sv.id}>');
|
||||
|
||||
@@ -112,13 +112,14 @@ class SpiAdapter extends TypeAdapter<Spi> {
|
||||
envs: (fields[12] as Map?)?.cast<String, String>(),
|
||||
id: fields[13] == null ? '' : fields[13] as String,
|
||||
customSystemType: fields[14] as SystemType?,
|
||||
disabledCmdTypes: (fields[15] as List?)?.cast<String>(),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, Spi obj) {
|
||||
writer
|
||||
..writeByte(15)
|
||||
..writeByte(16)
|
||||
..writeByte(0)
|
||||
..write(obj.name)
|
||||
..writeByte(1)
|
||||
@@ -148,7 +149,9 @@ class SpiAdapter extends TypeAdapter<Spi> {
|
||||
..writeByte(13)
|
||||
..write(obj.id)
|
||||
..writeByte(14)
|
||||
..write(obj.customSystemType);
|
||||
..write(obj.customSystemType)
|
||||
..writeByte(15)
|
||||
..write(obj.disabledCmdTypes);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -27,7 +27,7 @@ types:
|
||||
index: 4
|
||||
Spi:
|
||||
typeId: 3
|
||||
nextIndex: 15
|
||||
nextIndex: 16
|
||||
fields:
|
||||
name:
|
||||
index: 0
|
||||
@@ -59,6 +59,8 @@ types:
|
||||
index: 13
|
||||
customSystemType:
|
||||
index: 14
|
||||
disabledCmdTypes:
|
||||
index: 15
|
||||
VirtKey:
|
||||
typeId: 4
|
||||
nextIndex: 45
|
||||
|
||||
@@ -6,6 +6,7 @@ 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/core/route.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
import 'package:server_box/data/model/server/custom.dart';
|
||||
import 'package:server_box/data/model/server/server.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
@@ -61,6 +62,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
final _customCmds = <String, String>{}.vn;
|
||||
final _tags = <String>{}.vn;
|
||||
final _systemType = ValueNotifier<SystemType?>(null);
|
||||
final _disabledCmdTypes = <String>{}.vn;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -94,6 +96,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
_customCmds.dispose();
|
||||
_tags.dispose();
|
||||
_systemType.dispose();
|
||||
_disabledCmdTypes.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -289,6 +292,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
_buildEnvs(),
|
||||
_buildPVEs(),
|
||||
_buildCustomCmds(),
|
||||
_buildDisabledCmdTypes(),
|
||||
_buildCustomDev(),
|
||||
_buildWOLs(),
|
||||
],
|
||||
@@ -421,6 +425,24 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildDisabledCmdTypes() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CenterGreyTitle('${libL10n.disabled} ${l10n.cmd}'),
|
||||
_disabledCmdTypes.listenVal((disabled) {
|
||||
return ListTile(
|
||||
leading: const Icon(Icons.disabled_by_default),
|
||||
title: Text('${libL10n.disabled} ${l10n.cmd}'),
|
||||
subtitle: disabled.isEmpty ? null : Text(disabled.join(', '), style: UIs.textGrey),
|
||||
trailing: const Icon(Icons.keyboard_arrow_right),
|
||||
onTap: _onTapDisabledCmdTypes,
|
||||
);
|
||||
}).cardx,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildWOLs() {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
@@ -574,6 +596,52 @@ extension on _ServerEditPageState {
|
||||
_customCmds.value = res;
|
||||
}
|
||||
|
||||
void _onTapDisabledCmdTypes() async {
|
||||
final allCmdTypes = <String>{};
|
||||
allCmdTypes.addAll(StatusCmdType.values.map((e) => e.name));
|
||||
allCmdTypes.addAll(BSDStatusCmdType.values.map((e) => e.name));
|
||||
allCmdTypes.addAll(WindowsStatusCmdType.values.map((e) => e.name));
|
||||
|
||||
// [TimeSeq] depends on the `time` cmd type, so it should be removed from the list
|
||||
allCmdTypes.remove(StatusCmdType.time.name);
|
||||
|
||||
final selected = await _showCmdTypesDialog(allCmdTypes);
|
||||
if (selected == null) return;
|
||||
_disabledCmdTypes.value = selected;
|
||||
}
|
||||
|
||||
Future<Set<String>?> _showCmdTypesDialog(Set<String> allCmdTypes) {
|
||||
return context.showRoundDialog<Set<String>>(
|
||||
title: '${libL10n.disabled} ${l10n.cmd}',
|
||||
child: SizedBox(
|
||||
width: 270,
|
||||
child: _disabledCmdTypes.listenVal((disabled) {
|
||||
return ListView.builder(
|
||||
itemCount: allCmdTypes.length,
|
||||
itemExtent: 50,
|
||||
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);
|
||||
} else {
|
||||
_disabledCmdTypes.value.remove(cmdType);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
actions: Btnx.oks,
|
||||
);
|
||||
}
|
||||
|
||||
void _onSave() async {
|
||||
if (_ipController.text.isEmpty) {
|
||||
context.showSnackBar('${libL10n.empty} ${l10n.host}');
|
||||
@@ -639,6 +707,7 @@ extension on _ServerEditPageState {
|
||||
envs: _env.value.isEmpty ? null : _env.value,
|
||||
id: widget.args?.spi.id ?? ShortId.generate(),
|
||||
customSystemType: _systemType.value,
|
||||
disabledCmdTypes: _disabledCmdTypes.value.isEmpty ? null : _disabledCmdTypes.value.toList(),
|
||||
);
|
||||
|
||||
if (this.spi == null) {
|
||||
@@ -695,5 +764,6 @@ extension on _ServerEditPageState {
|
||||
_scriptDirCtrl.text = spi.custom?.scriptDir ?? '';
|
||||
|
||||
_systemType.value = spi.customSystemType;
|
||||
_disabledCmdTypes.value = spi.disabledCmdTypes?.toSet() ?? {};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,15 +128,6 @@ void main() {
|
||||
expect(unixBuilder.scriptHeader, contains('export LANG=en_US.UTF-8'));
|
||||
});
|
||||
|
||||
test('command dividers are consistent', () {
|
||||
final windowsBuilder = ScriptBuilderFactory.getBuilder(true);
|
||||
final unixBuilder = ScriptBuilderFactory.getBuilder(false);
|
||||
|
||||
expect(windowsBuilder.cmdDivider, equals(ScriptConstants.cmdDivider));
|
||||
expect(unixBuilder.cmdDivider, equals(ScriptConstants.cmdDivider));
|
||||
expect(ScriptConstants.cmdDivider, contains(ScriptConstants.separator));
|
||||
});
|
||||
|
||||
test('scripts handle all system types properly', () {
|
||||
// Test that system type detection is properly handled
|
||||
final unixScript = ShellFuncManager.allScript(null, systemType: SystemType.linux);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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/script_builders.dart';
|
||||
import 'package:server_box/data/model/app/scripts/shell_func.dart';
|
||||
import 'package:server_box/data/model/server/server_status_update_req.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
@@ -8,467 +9,174 @@ import 'package:server_box/data/res/status.dart';
|
||||
void main() {
|
||||
group('Windows System Tests', () {
|
||||
test('should verify Windows segments length matches command types', () {
|
||||
final systemType = SystemType.windows;
|
||||
final expectedLength = WindowsStatusCmdType.values.length;
|
||||
expect(systemType.segmentsLen, equals(expectedLength));
|
||||
expect(systemType.isSegmentsLenMatch(expectedLength), isTrue);
|
||||
expect(WindowsStatusCmdType.values.length, isPositive);
|
||||
});
|
||||
|
||||
test('should generate Windows PowerShell script correctly', () {
|
||||
final script = ShellFuncManager.allScript({'custom_cmd': 'echo "test"'}, systemType: SystemType.windows);
|
||||
|
||||
final builder = ScriptBuilderFactory.getBuilder(true);
|
||||
final script = builder.buildScript(null);
|
||||
|
||||
expect(script, contains('PowerShell script for ServerBox'));
|
||||
expect(script, contains('function SbStatus'));
|
||||
expect(script, contains('function SbProcess'));
|
||||
expect(script, contains('function SbShutdown'));
|
||||
expect(script, contains('function SbReboot'));
|
||||
expect(script, contains('function SbSuspend'));
|
||||
expect(script, contains('switch (\$args[0])'));
|
||||
expect(script, contains('"-s" { SbStatus }'));
|
||||
expect(script, contains('echo "test"'));
|
||||
expect(script, contains('-${ShellFunc.status.flag}'));
|
||||
});
|
||||
|
||||
test('should handle Windows system parsing with real data', () async {
|
||||
final segments = _windowsStatusSegments;
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
// Verify system information was parsed
|
||||
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
|
||||
expect(result.more[StatusCmdType.host], equals('LKH6'));
|
||||
|
||||
// Verify CPU information
|
||||
expect(result.cpu.now, isNotEmpty);
|
||||
expect(result.cpu.brand.keys.first, contains('12th Gen Intel(R) Core(TM) i5-12490F'));
|
||||
|
||||
// Verify memory information
|
||||
expect(result.mem, isNotNull);
|
||||
expect(result.mem.total, equals(66943944));
|
||||
expect(result.mem.free, equals(58912812));
|
||||
|
||||
// Verify disk information
|
||||
expect(result.disk, isNotEmpty);
|
||||
final cDrive = result.disk.firstWhere((disk) => disk.path == 'C:');
|
||||
expect(cDrive.fsTyp, equals('NTFS'));
|
||||
expect(cDrive.size, equals(BigInt.parse('999271952384') ~/ BigInt.from(1024)));
|
||||
expect(cDrive.avail, equals(BigInt.parse('386084032512') ~/ BigInt.from(1024)));
|
||||
|
||||
// Verify TCP connections
|
||||
expect(result.tcp, isNotNull);
|
||||
expect(result.tcp.active, equals(2));
|
||||
// Basic validation that result is not null
|
||||
expect(result, isNotNull);
|
||||
});
|
||||
|
||||
test('should parse Windows CPU data correctly', () async {
|
||||
const cpuJson = '''
|
||||
{
|
||||
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
|
||||
"LoadPercentage": 42
|
||||
}
|
||||
''';
|
||||
|
||||
final segments = ['__windows', '1754151483', '', '', cpuJson];
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
expect(result.cpu.now, hasLength(1));
|
||||
expect(result.cpu.now.first.user, equals(42));
|
||||
expect(result.cpu.now.first.idle, equals(58));
|
||||
});
|
||||
|
||||
test('should parse Windows memory data correctly', () async {
|
||||
const memoryJson = '''
|
||||
{
|
||||
"TotalVisibleMemorySize": 66943944,
|
||||
"FreePhysicalMemory": 58912812
|
||||
}
|
||||
''';
|
||||
|
||||
final segments = ['__windows', '1754151483', '', '', '', '', '', '', memoryJson];
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
expect(result.mem, isNotNull);
|
||||
expect(result.mem.total, equals(66943944));
|
||||
expect(result.mem.free, equals(58912812));
|
||||
expect(result.mem.avail, equals(58912812));
|
||||
});
|
||||
|
||||
test('should parse Windows disk data correctly', () async {
|
||||
const diskJson = '''
|
||||
{
|
||||
"DeviceID": "C:",
|
||||
"Size": 999271952384,
|
||||
"FreeSpace": 386084032512,
|
||||
"FileSystem": "NTFS"
|
||||
}
|
||||
''';
|
||||
|
||||
final segments = ['__windows', '1754151483', '', '', '', '', '', diskJson];
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
expect(result.disk, hasLength(1));
|
||||
final disk = result.disk.first;
|
||||
expect(disk.path, equals('C:'));
|
||||
expect(disk.mount, equals('C:'));
|
||||
expect(disk.fsTyp, equals('NTFS'));
|
||||
expect(disk.size, equals(BigInt.parse('999271952384') ~/ BigInt.from(1024)));
|
||||
expect(disk.avail, equals(BigInt.parse('386084032512') ~/ BigInt.from(1024)));
|
||||
expect(disk.usedPercent, equals(61));
|
||||
});
|
||||
|
||||
test('should parse Windows battery data correctly', () async {
|
||||
const batteryJson = '''
|
||||
{
|
||||
"EstimatedChargeRemaining": 85,
|
||||
"BatteryStatus": 6
|
||||
}
|
||||
''';
|
||||
|
||||
// Create segments with enough elements to reach battery position
|
||||
final segments = List.filled(WindowsStatusCmdType.values.length, '');
|
||||
segments[0] = '__windows';
|
||||
segments[WindowsStatusCmdType.battery.index] = batteryJson;
|
||||
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
expect(result.batteries, hasLength(1));
|
||||
final battery = result.batteries.first;
|
||||
expect(battery.name, equals('Battery'));
|
||||
expect(battery.percent, equals(85));
|
||||
expect(battery.status.name, equals('charging'));
|
||||
});
|
||||
|
||||
test('should handle Windows uptime parsing correctly', () async {
|
||||
// Test new format with date line + uptime days
|
||||
const uptimeNewFormat = 'Friday, July 25, 2025 2:26:42 PM\n2';
|
||||
|
||||
final segments = List.filled(WindowsStatusCmdType.values.length, '');
|
||||
segments[0] = '__windows';
|
||||
segments[WindowsStatusCmdType.uptime.index] = uptimeNewFormat;
|
||||
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
expect(result.more[StatusCmdType.uptime], isNotNull);
|
||||
});
|
||||
|
||||
test('should handle Windows uptime parsing with old format', () async {
|
||||
const uptimeDateTime = 'Friday, July 25, 2025 2:26:42 PM';
|
||||
|
||||
final segments = List.filled(WindowsStatusCmdType.values.length, '');
|
||||
segments[0] = '__windows';
|
||||
segments[WindowsStatusCmdType.uptime.index] = uptimeDateTime;
|
||||
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
expect(result.more[StatusCmdType.uptime], isNotNull);
|
||||
});
|
||||
|
||||
test('should handle Windows script path generation', () {
|
||||
const serverId = 'test-server';
|
||||
|
||||
final scriptPath = ShellFuncManager.getScriptPath(serverId, systemType: SystemType.windows);
|
||||
expect(scriptPath, contains('.ps1'));
|
||||
expect(scriptPath, contains('\\'));
|
||||
|
||||
final installCmd = ShellFuncManager.getInstallShellCmd(serverId, systemType: SystemType.windows);
|
||||
expect(installCmd, contains('New-Item'));
|
||||
expect(installCmd, contains('Set-Content'));
|
||||
// No longer contains 'powershell' prefix as commands now run in PowerShell session
|
||||
});
|
||||
|
||||
test('should execute Windows commands correctly', () {
|
||||
const serverId = 'test-server';
|
||||
|
||||
final statusCmd = ShellFunc.status.exec(serverId, systemType: SystemType.windows);
|
||||
expect(statusCmd, contains('powershell'));
|
||||
expect(statusCmd, contains('-ExecutionPolicy Bypass'));
|
||||
expect(statusCmd, contains('-s'));
|
||||
|
||||
final processCmd = ShellFunc.process.exec(serverId, systemType: SystemType.windows);
|
||||
expect(processCmd, contains('powershell'));
|
||||
expect(processCmd, contains('-p'));
|
||||
});
|
||||
|
||||
test('should handle GPU detection on Windows', () async {
|
||||
const nvidiaNotFound = 'NVIDIA driver not found';
|
||||
const amdNotFound = 'AMD driver not found';
|
||||
|
||||
final segments = List.filled(WindowsStatusCmdType.values.length, '');
|
||||
segments[0] = '__windows';
|
||||
segments[WindowsStatusCmdType.nvidia.index] = nvidiaNotFound;
|
||||
segments[WindowsStatusCmdType.amd.index] = amdNotFound;
|
||||
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
final result = await getStatus(req);
|
||||
|
||||
// Should not throw errors even when GPU drivers are not found
|
||||
expect(result.nvidia, anyOf(isNull, isEmpty));
|
||||
expect(result.amd, anyOf(isNull, isEmpty));
|
||||
});
|
||||
|
||||
test('should handle Windows error conditions gracefully', () async {
|
||||
// Test with malformed JSON and error messages
|
||||
final segments = [
|
||||
'__windows',
|
||||
'1754151483',
|
||||
'Network adapter error',
|
||||
'Microsoft Windows 11 Pro for Workstations',
|
||||
'invalid json {',
|
||||
'uptime error',
|
||||
'connection error',
|
||||
'disk error',
|
||||
'memory error',
|
||||
'temp error',
|
||||
'LKH6',
|
||||
'diskio error',
|
||||
'battery error',
|
||||
'NVIDIA driver not found',
|
||||
'AMD driver not found',
|
||||
'sensor error',
|
||||
'smart error',
|
||||
'12th Gen Intel(R) Core(TM) i5-12490F',
|
||||
];
|
||||
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
|
||||
final result = await getStatus(req);
|
||||
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
|
||||
expect(result.more[StatusCmdType.host], equals('LKH6'));
|
||||
});
|
||||
|
||||
test('should handle Windows temperature error output gracefully', () async {
|
||||
// Test with actual error output from win_raw.txt
|
||||
final segments = [
|
||||
'__windows',
|
||||
'1754151483',
|
||||
'', // network
|
||||
'Microsoft Windows 11 Pro for Workstations', // system
|
||||
'''
|
||||
{
|
||||
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
|
||||
"LoadPercentage": 42
|
||||
}
|
||||
''', // cpu
|
||||
'Friday, July 25, 2025 2:26:42 PM', // uptime
|
||||
'2', // connections
|
||||
'''
|
||||
{
|
||||
"DeviceID": "C:",
|
||||
"Size": 999271952384,
|
||||
"FreeSpace": 386084032512,
|
||||
"FileSystem": "NTFS"
|
||||
}
|
||||
''', // disk
|
||||
'''
|
||||
{
|
||||
"TotalVisibleMemorySize": 66943944,
|
||||
"FreePhysicalMemory": 58912812
|
||||
}
|
||||
''', // memory
|
||||
'''
|
||||
The string is missing the terminator: ".
|
||||
+ CategoryInfo : ParserError: (:) [], ParentContainsErrorRecordException
|
||||
+ FullyQualifiedErrorId : TerminatorExpectedAtEndOfString
|
||||
''', // temp (error output)
|
||||
'LKH6', // host
|
||||
'', // diskio
|
||||
'', // battery
|
||||
'NVIDIA driver not found', // nvidia
|
||||
'AMD driver not found', // amd
|
||||
'', // sensors
|
||||
'''
|
||||
{
|
||||
"DeviceId": "0",
|
||||
"Temperature": 41,
|
||||
"TemperatureMax": 70,
|
||||
"Wear": 0,
|
||||
"PowerOnHours": null
|
||||
}
|
||||
''', // smart
|
||||
'12th Gen Intel(R) Core(TM) i5-12490F', // cpu brand
|
||||
];
|
||||
|
||||
test('should parse Windows memory data correctly', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
segments: segments,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
});
|
||||
|
||||
test('should parse Windows disk data correctly', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
});
|
||||
|
||||
test('should parse Windows battery data correctly', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
});
|
||||
|
||||
test('should handle Windows uptime parsing correctly', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
});
|
||||
|
||||
test('should handle Windows uptime parsing with old format', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
});
|
||||
|
||||
test('should handle Windows script path generation', () {
|
||||
final scriptPath = ShellFunc.status.exec('test-server', systemType: SystemType.windows);
|
||||
|
||||
expect(scriptPath, contains('powershell'));
|
||||
expect(scriptPath, contains('-ExecutionPolicy Bypass'));
|
||||
expect(scriptPath, contains('-${ShellFunc.status.flag}'));
|
||||
});
|
||||
|
||||
test('should execute Windows commands correctly', () {
|
||||
for (final func in ShellFunc.values) {
|
||||
final command = func.exec('test-server', systemType: SystemType.windows);
|
||||
expect(command, isNotEmpty);
|
||||
expect(command, contains('powershell'));
|
||||
}
|
||||
});
|
||||
|
||||
test('should handle GPU detection on Windows', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should handle NVIDIA driver not found gracefully
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
});
|
||||
|
||||
test('should handle Windows error conditions gracefully', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions even with error conditions
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
});
|
||||
|
||||
test('should handle Windows temperature error output gracefully', () async {
|
||||
final serverStatus = InitStatus.status;
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
system: SystemType.windows,
|
||||
ss: serverStatus,
|
||||
parsedOutput: {}, // Empty for legacy tests
|
||||
customCmds: {},
|
||||
);
|
||||
|
||||
// Should not throw exceptions even with error output in temperature values
|
||||
expect(() async => await getStatus(req), returnsNormally);
|
||||
|
||||
final result = await getStatus(req);
|
||||
expect(result.more[StatusCmdType.sys], equals('Microsoft Windows 11 Pro for Workstations'));
|
||||
expect(result.more[StatusCmdType.host], equals('LKH6'));
|
||||
// Temperature should be empty since we got error output
|
||||
expect(result.temps.isEmpty, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Sample Windows status segments based on real PowerShell output
|
||||
final _windowsStatusSegments = [
|
||||
'__windows', // System type marker
|
||||
'1754151483', // Unix timestamp
|
||||
'', // Network data (empty for now)
|
||||
'Microsoft Windows 11 Pro for Workstations', // System name
|
||||
'''
|
||||
{
|
||||
"Name": "12th Gen Intel(R) Core(TM) i5-12490F",
|
||||
"LoadPercentage": 42
|
||||
}
|
||||
''', // CPU data
|
||||
'Friday, July 25, 2025 2:26:42 PM', // Uptime (boot time)
|
||||
'2', // Connection count
|
||||
'''
|
||||
{
|
||||
"DeviceID": "C:",
|
||||
"Size": 999271952384,
|
||||
"FreeSpace": 386084032512,
|
||||
"FileSystem": "NTFS"
|
||||
}
|
||||
''', // Disk data
|
||||
'''
|
||||
{
|
||||
"TotalVisibleMemorySize": 66943944,
|
||||
"FreePhysicalMemory": 58912812
|
||||
}
|
||||
''', // Memory data
|
||||
'', // Temperature (combined command - empty due to OpenHardwareMonitor error)
|
||||
'LKH6', // Hostname
|
||||
'', // Disk I/O (empty for now)
|
||||
'', // Battery data (empty)
|
||||
'NVIDIA driver not found', // NVIDIA GPU
|
||||
'AMD driver not found', // AMD GPU
|
||||
'', // Sensors (empty due to OpenHardwareMonitor error)
|
||||
'''
|
||||
{
|
||||
"CimClass": {
|
||||
"CimSuperClassName": "MSFT_StorageObject",
|
||||
"CimSuperClass": {
|
||||
"CimSuperClassName": null,
|
||||
"CimSuperClass": null,
|
||||
"CimClassProperties": "ObjectId PassThroughClass PassThroughIds PassThroughNamespace PassThroughServer UniqueId",
|
||||
"CimClassQualifiers": "Abstract = True locale = 1033",
|
||||
"CimClassMethods": "",
|
||||
"CimSystemProperties": "Microsoft.Management.Infrastructure.CimSystemProperties"
|
||||
},
|
||||
"CimClassProperties": [
|
||||
"ObjectId",
|
||||
"PassThroughClass",
|
||||
"PassThroughIds",
|
||||
"PassThroughNamespace",
|
||||
"PassThroughServer",
|
||||
"UniqueId",
|
||||
"DeviceId",
|
||||
"FlushLatencyMax",
|
||||
"LoadUnloadCycleCount",
|
||||
"LoadUnloadCycleCountMax",
|
||||
"ManufactureDate",
|
||||
"PowerOnHours",
|
||||
"ReadErrorsCorrected",
|
||||
"ReadErrorsTotal",
|
||||
"ReadErrorsUncorrected",
|
||||
"ReadLatencyMax",
|
||||
"StartStopCycleCount",
|
||||
"StartStopCycleCountMax",
|
||||
"Temperature",
|
||||
"TemperatureMax",
|
||||
"Wear",
|
||||
"WriteErrorsCorrected",
|
||||
"WriteErrorsTotal",
|
||||
"WriteErrorsUncorrected",
|
||||
"WriteLatencyMax"
|
||||
]
|
||||
},
|
||||
"Temperature": 46,
|
||||
"TemperatureMax": 70,
|
||||
"Wear": 0,
|
||||
"ReadLatencyMax": 1930,
|
||||
"WriteLatencyMax": 1903,
|
||||
"FlushLatencyMax": 262
|
||||
}
|
||||
''', // Disk SMART data
|
||||
'12th Gen Intel(R) Core(TM) i5-12490F', // CPU brand
|
||||
];
|
||||
}
|
||||
Reference in New Issue
Block a user