mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 15:24:35 +01:00
* feat: win compatibility * fix * fix: uptime parse * opt.: linux uptime accuracy * fix: windows temperature fetching * opt. * opt.: powershell exec * refactor: address PR review feedback and improve code quality ### Major Improvements: - **Refactored Windows status parsing**: Broke down large `_getWindowsStatus` method into 13 smaller, focused helper methods for better maintainability and readability - **Extracted system detection logic**: Created dedicated `SystemDetector` helper class to separate OS detection concerns from ServerProvider - **Improved concurrency handling**: Implemented proper synchronization for server updates using Future-based locks to prevent race conditions ### Bug Fixes: - **Fixed CPU percentage parsing**: Removed incorrect '*100' multiplication in BSD CPU parsing (values were already percentages) - **Enhanced memory parsing**: Added validation and error handling to BSD memory fallback parsing with proper logging - **Improved uptime parsing**: Added support for multiple Windows date formats and robust error handling with validation - **Fixed division by zero**: Added safety checks in Swap.usedPercent getter ### Code Quality Enhancements: - **Added comprehensive documentation**: Documented Windows CPU counter limitations and approach - **Strengthened error handling**: Added detailed logging and validation throughout parsing methods - **Improved robustness**: Enhanced BSD CPU parsing with percentage validation and warnings - **Better separation of concerns**: Each parsing method now has single responsibility ### Files Changed: - `lib/data/helper/system_detector.dart` (new): System detection helper - `lib/data/model/server/cpu.dart`: Fixed percentage parsing and added validation - `lib/data/model/server/memory.dart`: Enhanced fallback parsing and division-by-zero protection - `lib/data/model/server/server_status_update_req.dart`: Refactored into 13 focused parsing methods - `lib/data/provider/server.dart`: Improved synchronization and extracted system detection 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: parse & shell fn struct --------- Co-authored-by: Claude <noreply@anthropic.com>
159 lines
4.2 KiB
Dart
159 lines
4.2 KiB
Dart
import 'package:fl_lib/fl_lib.dart';
|
|
import 'package:server_box/core/extension/ssh_client.dart';
|
|
import 'package:server_box/data/model/app/shell_func.dart';
|
|
import 'package:server_box/data/model/server/server.dart';
|
|
import 'package:server_box/data/model/server/server_private_info.dart';
|
|
import 'package:server_box/data/model/server/systemd.dart';
|
|
import 'package:server_box/data/provider/server.dart';
|
|
|
|
final class SystemdProvider {
|
|
late final VNode<Server> _si;
|
|
late final bool _isRoot;
|
|
|
|
SystemdProvider.init(Spi spi) {
|
|
_isRoot = spi.isRoot;
|
|
_si = ServerProvider.pick(spi: spi)!;
|
|
getUnits();
|
|
}
|
|
|
|
final isBusy = false.vn;
|
|
final units = <SystemdUnit>[].vn;
|
|
|
|
void dispose() {
|
|
isBusy.dispose();
|
|
units.dispose();
|
|
}
|
|
|
|
Future<void> getUnits() async {
|
|
isBusy.value = true;
|
|
|
|
try {
|
|
final client = _si.value.client;
|
|
final result = await client!.execForOutput(_getUnitsCmd);
|
|
final units = result.split('\n');
|
|
|
|
final userUnits = <String>[];
|
|
final systemUnits = <String>[];
|
|
for (final unit in units) {
|
|
if (unit.startsWith('/etc/systemd/system')) {
|
|
systemUnits.add(unit);
|
|
} else if (unit.startsWith('~/.config/systemd/user')) {
|
|
userUnits.add(unit);
|
|
} else if (unit.trim().isNotEmpty) {
|
|
Loggers.app.warning('Unknown unit: $unit');
|
|
}
|
|
}
|
|
|
|
final parsedUserUnits = await _parseUnitObj(userUnits, SystemdUnitScope.user);
|
|
final parsedSystemUnits = await _parseUnitObj(systemUnits, SystemdUnitScope.system);
|
|
this.units.value = [...parsedUserUnits, ...parsedSystemUnits];
|
|
} catch (e, s) {
|
|
Loggers.app.warning('Parse systemd', e, s);
|
|
}
|
|
|
|
isBusy.value = false;
|
|
}
|
|
|
|
Future<List<SystemdUnit>> _parseUnitObj(List<String> unitNames, SystemdUnitScope scope) async {
|
|
final unitNames_ = unitNames.map((e) => e.trim().split('/').last.split('.').first).toList();
|
|
final script =
|
|
'''
|
|
for unit in ${unitNames_.join(' ')}; do
|
|
state=\$(systemctl show --no-pager \$unit)
|
|
echo -n "${ShellFunc.seperator}\n\$state"
|
|
done
|
|
''';
|
|
final client = _si.value.client!;
|
|
final result = await client.execForOutput(script);
|
|
final units = result.split(ShellFunc.seperator);
|
|
|
|
final parsedUnits = <SystemdUnit>[];
|
|
for (final unit in units) {
|
|
final parts = unit.split('\n');
|
|
var name = '';
|
|
var type = '';
|
|
var state = '';
|
|
String? description;
|
|
for (final part in parts) {
|
|
if (part.startsWith('Id=')) {
|
|
final val = _getIniVal(part).split('.');
|
|
name = val.first;
|
|
type = val.last;
|
|
continue;
|
|
}
|
|
if (part.startsWith('ActiveState=')) {
|
|
state = _getIniVal(part);
|
|
continue;
|
|
}
|
|
if (part.startsWith('Description=')) {
|
|
description = _getIniVal(part);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
final unitType = SystemdUnitType.fromString(type);
|
|
if (unitType == null) {
|
|
Loggers.app.warning('Unit type: $type');
|
|
continue;
|
|
}
|
|
final unitState = SystemdUnitState.fromString(state);
|
|
if (unitState == null) {
|
|
Loggers.app.warning('Unit state: $state');
|
|
continue;
|
|
}
|
|
|
|
parsedUnits.add(
|
|
SystemdUnit(name: name, type: unitType, scope: scope, state: unitState, description: description),
|
|
);
|
|
}
|
|
|
|
parsedUnits.sort((a, b) {
|
|
// user units first
|
|
if (a.scope != b.scope) {
|
|
return a.scope == SystemdUnitScope.user ? -1 : 1;
|
|
}
|
|
// active units first
|
|
if (a.state != b.state) {
|
|
return a.state == SystemdUnitState.active ? -1 : 1;
|
|
}
|
|
return a.name.compareTo(b.name);
|
|
});
|
|
return parsedUnits;
|
|
}
|
|
|
|
late final _getUnitsCmd =
|
|
'''
|
|
get_files() {
|
|
unit_type=\$1
|
|
base_dir=\$2
|
|
|
|
# If base_dir is not a directory, return
|
|
if [ ! -d "\$base_dir" ]; then
|
|
return
|
|
fi
|
|
|
|
find "\$base_dir" -type f -name "*.\$unit_type" -print | sort
|
|
}
|
|
|
|
get_type_files() {
|
|
unit_type=\$1
|
|
base_dir=""
|
|
|
|
${_isRoot ? """
|
|
get_files \$unit_type /etc/systemd/system
|
|
get_files \$unit_type ~/.config/systemd/user""" : """
|
|
get_files \$unit_type ~/.config/systemd/user"""}
|
|
}
|
|
|
|
types="service socket mount timer"
|
|
|
|
for type in \$types; do
|
|
get_type_files \$type
|
|
done
|
|
''';
|
|
}
|
|
|
|
String _getIniVal(String line) {
|
|
return line.split('=').last;
|
|
}
|