mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
178 lines
5.4 KiB
Dart
178 lines
5.4 KiB
Dart
import 'package:fl_lib/fl_lib.dart';
|
|
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import 'package:server_box/core/extension/ssh_client.dart';
|
|
import 'package:server_box/data/model/app/scripts/script_consts.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/single.dart';
|
|
|
|
part 'systemd.freezed.dart';
|
|
part 'systemd.g.dart';
|
|
|
|
@freezed
|
|
abstract class SystemdState with _$SystemdState {
|
|
const factory SystemdState({
|
|
@Default(false) bool isBusy,
|
|
@Default(<SystemdUnit>[]) List<SystemdUnit> units,
|
|
@Default(SystemdScopeFilter.all) SystemdScopeFilter scopeFilter,
|
|
}) = _SystemdState;
|
|
}
|
|
|
|
@riverpod
|
|
class SystemdNotifier extends _$SystemdNotifier {
|
|
late final ServerState _si;
|
|
|
|
@override
|
|
SystemdState build(Spi spi) {
|
|
final si = ref.read(serverProvider(spi.id));
|
|
_si = si;
|
|
// Async initialization
|
|
Future.microtask(() => getUnits());
|
|
return const SystemdState();
|
|
}
|
|
|
|
List<SystemdUnit> get filteredUnits {
|
|
switch (state.scopeFilter) {
|
|
case SystemdScopeFilter.all:
|
|
return state.units;
|
|
case SystemdScopeFilter.system:
|
|
return state.units.where((unit) => unit.scope == SystemdUnitScope.system).toList();
|
|
case SystemdScopeFilter.user:
|
|
return state.units.where((unit) => unit.scope == SystemdUnitScope.user).toList();
|
|
}
|
|
}
|
|
|
|
void setScopeFilter(SystemdScopeFilter filter) {
|
|
state = state.copyWith(scopeFilter: filter);
|
|
}
|
|
|
|
Future<void> getUnits() async {
|
|
state = state.copyWith(isBusy: true);
|
|
|
|
try {
|
|
final client = _si.client;
|
|
final result = await client!.execForOutput(_getUnitsCmd);
|
|
final units = result.split('\n');
|
|
|
|
final userUnits = <String>[];
|
|
final systemUnits = <String>[];
|
|
for (final unit in units) {
|
|
final maybeSystem = unit.contains('/systemd/system');
|
|
final maybeUser = unit.contains('/.config/systemd/user');
|
|
if (maybeSystem && !maybeUser) {
|
|
systemUnits.add(unit);
|
|
} else {
|
|
userUnits.add(unit);
|
|
}
|
|
}
|
|
|
|
final parsedUserUnits = await _parseUnitObj(userUnits, SystemdUnitScope.user);
|
|
final parsedSystemUnits = await _parseUnitObj(systemUnits, SystemdUnitScope.system);
|
|
state = state.copyWith(units: [...parsedUserUnits, ...parsedSystemUnits], isBusy: false);
|
|
} catch (e, s) {
|
|
dprint('Parse systemd', e, s);
|
|
state = state.copyWith(isBusy: 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 "\$state"
|
|
echo -n "\n${ScriptConstants.separator}\n"
|
|
done
|
|
''';
|
|
final client = _si.client!;
|
|
final result = await client.execForOutput(script);
|
|
final units = result.split(ScriptConstants.separator);
|
|
|
|
final parsedUnits = <SystemdUnit>[];
|
|
for (final unit in units.where((e) => e.trim().isNotEmpty)) {
|
|
final parts = unit.split('\n').where((e) => e.trim().isNotEmpty).toList();
|
|
if (parts.isEmpty) continue;
|
|
var name = '';
|
|
var type = '';
|
|
var state = '';
|
|
String? description;
|
|
for (final part in parts) {
|
|
if (part.startsWith('Id=')) {
|
|
final val = _getIniVal(part);
|
|
if (val == null) continue;
|
|
// Id=sshd.service
|
|
final vals = val.split('.');
|
|
name = vals.first;
|
|
type = vals.last;
|
|
continue;
|
|
}
|
|
if (part.startsWith('ActiveState=')) {
|
|
final val = _getIniVal(part);
|
|
if (val == null) continue;
|
|
state = val;
|
|
continue;
|
|
}
|
|
if (part.startsWith('Description=')) {
|
|
description = _getIniVal(part);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
final unitType = SystemdUnitType.fromString(type);
|
|
if (unitType == null) {
|
|
dprint('Unit type: $type');
|
|
continue;
|
|
}
|
|
final unitState = SystemdUnitState.fromString(state);
|
|
if (unitState == null) {
|
|
dprint('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 = '''
|
|
types="service socket mount timer"
|
|
|
|
get_files() {
|
|
unit_type=\$1
|
|
base_dir=\$2
|
|
[ -d "\$base_dir" ] || return
|
|
find "\$base_dir" -type f -name "*.\$unit_type" -print
|
|
}
|
|
|
|
for type in \$types; do
|
|
get_files \$type /etc/systemd/system
|
|
# Parsing these paths can lead to SSH transport closed errors
|
|
# get_files \$type /lib/systemd/system
|
|
# get_files \$type /usr/lib/systemd/system
|
|
get_files \$type ~/.config/systemd/user
|
|
done | sort
|
|
''';
|
|
}
|
|
|
|
String? _getIniVal(String line) {
|
|
final idx = line.indexOf('=');
|
|
if (idx < 0) return null;
|
|
return line.substring(idx + 1).trim();
|
|
}
|