mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-17 07:14:28 +01:00
feat: systemd management (#532)
This commit is contained in:
@@ -690,7 +690,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -700,7 +700,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -826,7 +826,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -836,7 +836,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -854,7 +854,7 @@
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_BITCODE = NO;
|
||||
INFOPLIST_FILE = "Runner/Info-$(CONFIGURATION).plist";
|
||||
@@ -864,7 +864,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||
@@ -885,7 +885,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -898,7 +898,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
@@ -924,7 +924,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -937,7 +937,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -960,7 +960,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
@@ -973,7 +973,7 @@
|
||||
"@executable_path/Frameworks",
|
||||
"@executable_path/../../Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.StatusWidget;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
@@ -996,7 +996,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1008,7 +1008,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
@@ -1037,7 +1037,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1049,7 +1049,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
PRODUCT_NAME = ServerBox;
|
||||
@@ -1075,7 +1075,7 @@
|
||||
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
|
||||
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_ASSET_PATHS = "";
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
ENABLE_PREVIEWS = YES;
|
||||
@@ -1087,7 +1087,7 @@
|
||||
"$(inherited)",
|
||||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
MTL_FAST_MATH = YES;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox.WatchEnd;
|
||||
PRODUCT_NAME = ServerBox;
|
||||
|
||||
@@ -13,6 +13,7 @@ typedef _OnStdin = void Function(SSHSession session);
|
||||
typedef PwdRequestFunc = Future<String?> Function(String? user);
|
||||
|
||||
extension SSHClientX on SSHClient {
|
||||
/// TODO: delete [exec]
|
||||
Future<SSHSession> exec(
|
||||
String cmd, {
|
||||
_OnStdout? onStderr,
|
||||
@@ -135,4 +136,36 @@ extension SSHClientX on SSHClient {
|
||||
|
||||
return result.takeBytes();
|
||||
}
|
||||
|
||||
Future<String> runScriptIn(
|
||||
String cmd, {
|
||||
String shell = '/bin/sh',
|
||||
bool stdout = true,
|
||||
bool stderr = true,
|
||||
}) async {
|
||||
final session = await execute('cat | $shell');
|
||||
|
||||
final result = BytesBuilder(copy: false);
|
||||
final stdoutDone = Completer<void>();
|
||||
final stderrDone = Completer<void>();
|
||||
|
||||
session.stdout.listen(
|
||||
stdout ? result.add : (_) {},
|
||||
onDone: stdoutDone.complete,
|
||||
onError: stderrDone.completeError,
|
||||
);
|
||||
session.stderr.listen(
|
||||
stderr ? result.add : (_) {},
|
||||
onDone: stderrDone.complete,
|
||||
onError: stderrDone.completeError,
|
||||
);
|
||||
|
||||
session.stdin.add('$cmd\n'.uint8List);
|
||||
session.stdin.close();
|
||||
|
||||
await stdoutDone.future;
|
||||
await stderrDone.future;
|
||||
|
||||
return result.takeBytes().string;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
import 'package:server_box/data/provider/app.dart';
|
||||
|
||||
abstract final class KeybordInteractive {
|
||||
static FutureOr<List<String>?> defaultHandle(
|
||||
@@ -12,8 +12,8 @@ abstract final class KeybordInteractive {
|
||||
BuildContext? ctx,
|
||||
}) async {
|
||||
try {
|
||||
final res = await (ctx ?? Pros.app.ctx)?.showPwdDialog(
|
||||
title: '2FA ${l10n.pwd}',
|
||||
final res = await (ctx ?? AppProvider.ctx)?.showPwdDialog(
|
||||
title: l10n.pwd,
|
||||
id: spi.id,
|
||||
label: spi.id,
|
||||
);
|
||||
|
||||
@@ -2,29 +2,47 @@ import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
part 'server_func.g.dart';
|
||||
|
||||
@HiveType(typeId: 6)
|
||||
enum ServerFuncBtn {
|
||||
@HiveField(0)
|
||||
terminal,
|
||||
terminal._(),
|
||||
@HiveField(1)
|
||||
sftp,
|
||||
sftp._(),
|
||||
@HiveField(2)
|
||||
container,
|
||||
container._(),
|
||||
@HiveField(3)
|
||||
process,
|
||||
process._(),
|
||||
//@HiveField(4)
|
||||
//pkg,
|
||||
@HiveField(5)
|
||||
snippet,
|
||||
snippet._(),
|
||||
@HiveField(6)
|
||||
iperf,
|
||||
iperf._(),
|
||||
// @HiveField(7)
|
||||
// pve,
|
||||
@HiveField(8)
|
||||
systemd._(1058),
|
||||
;
|
||||
|
||||
final int? addedVersion;
|
||||
|
||||
const ServerFuncBtn._([this.addedVersion]);
|
||||
|
||||
static void autoAddNewFuncs(int cur) {
|
||||
if (cur >= systemd.addedVersion!) {
|
||||
final prop = Stores.setting.serverFuncBtns;
|
||||
final list = prop.fetch();
|
||||
if (!list.contains(systemd.index)) {
|
||||
list.add(systemd.index);
|
||||
prop.put(list);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static final defaultIdxs = [
|
||||
terminal,
|
||||
sftp,
|
||||
@@ -32,6 +50,7 @@ enum ServerFuncBtn {
|
||||
process,
|
||||
//pkg,
|
||||
snippet,
|
||||
systemd,
|
||||
].map((e) => e.index).toList();
|
||||
|
||||
IconData get icon => switch (this) {
|
||||
@@ -42,6 +61,7 @@ enum ServerFuncBtn {
|
||||
process => Icons.list_alt_outlined,
|
||||
terminal => Icons.terminal,
|
||||
iperf => Icons.speed,
|
||||
systemd => MingCute.plugin_2_fill,
|
||||
};
|
||||
|
||||
String get toStr => switch (this) {
|
||||
@@ -52,5 +72,6 @@ enum ServerFuncBtn {
|
||||
process => l10n.process,
|
||||
terminal => l10n.terminal,
|
||||
iperf => 'iperf',
|
||||
systemd => 'Systemd',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
||||
return ServerFuncBtn.snippet;
|
||||
case 6:
|
||||
return ServerFuncBtn.iperf;
|
||||
case 8:
|
||||
return ServerFuncBtn.systemd;
|
||||
default:
|
||||
return ServerFuncBtn.terminal;
|
||||
}
|
||||
@@ -51,6 +53,9 @@ class ServerFuncBtnAdapter extends TypeAdapter<ServerFuncBtn> {
|
||||
case ServerFuncBtn.iperf:
|
||||
writer.writeByte(6);
|
||||
break;
|
||||
case ServerFuncBtn.systemd:
|
||||
writer.writeByte(8);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
118
lib/data/model/server/systemd.dart
Normal file
118
lib/data/model/server/systemd.dart
Normal file
@@ -0,0 +1,118 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
enum SystemdUnitFunc {
|
||||
start,
|
||||
stop,
|
||||
restart,
|
||||
reload,
|
||||
enable,
|
||||
disable,
|
||||
status,
|
||||
;
|
||||
|
||||
IconData get icon => switch (this) {
|
||||
start => Icons.play_arrow,
|
||||
stop => Icons.stop,
|
||||
restart => Icons.refresh,
|
||||
reload => Icons.refresh,
|
||||
enable => Icons.check,
|
||||
disable => Icons.close,
|
||||
status => Icons.info,
|
||||
};
|
||||
}
|
||||
|
||||
enum SystemdUnitType {
|
||||
service,
|
||||
socket,
|
||||
mount,
|
||||
timer,
|
||||
;
|
||||
|
||||
static SystemdUnitType? fromString(String? value) {
|
||||
return values.firstWhereOrNull((e) => e.name == value?.toLowerCase());
|
||||
}
|
||||
}
|
||||
|
||||
enum SystemdUnitScope {
|
||||
system,
|
||||
user,
|
||||
;
|
||||
|
||||
Color? get color => switch (this) {
|
||||
system => Colors.red,
|
||||
_ => null,
|
||||
};
|
||||
|
||||
String getCmdPrefix(bool isRoot) {
|
||||
if (this == system) {
|
||||
return isRoot ? 'systemctl' : 'sudo systemctl';
|
||||
}
|
||||
return 'systemctl --user';
|
||||
}
|
||||
}
|
||||
|
||||
enum SystemdUnitState {
|
||||
active,
|
||||
inactive,
|
||||
failed,
|
||||
activating,
|
||||
deactivating,
|
||||
;
|
||||
|
||||
static SystemdUnitState? fromString(String? value) {
|
||||
return values.firstWhereOrNull((e) => e.name == value?.toLowerCase());
|
||||
}
|
||||
|
||||
Color? get color => switch (this) {
|
||||
failed => Colors.red,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
|
||||
final class SystemdUnit {
|
||||
final String name;
|
||||
final String? description;
|
||||
final SystemdUnitType type;
|
||||
final SystemdUnitScope scope;
|
||||
final SystemdUnitState state;
|
||||
|
||||
SystemdUnit({
|
||||
required this.name,
|
||||
this.description,
|
||||
required this.type,
|
||||
required this.scope,
|
||||
required this.state,
|
||||
});
|
||||
|
||||
String getCmd({
|
||||
required SystemdUnitFunc func,
|
||||
required bool isRoot,
|
||||
}) {
|
||||
final prefix = scope.getCmdPrefix(isRoot);
|
||||
return '$prefix ${func.name} $name';
|
||||
}
|
||||
|
||||
List<SystemdUnitFunc> get availableFuncs {
|
||||
final funcs = <SystemdUnitFunc>{};
|
||||
switch (state) {
|
||||
case SystemdUnitState.active:
|
||||
funcs.addAll([SystemdUnitFunc.stop, SystemdUnitFunc.restart]);
|
||||
break;
|
||||
case SystemdUnitState.inactive:
|
||||
funcs.addAll([SystemdUnitFunc.start]);
|
||||
break;
|
||||
case SystemdUnitState.failed:
|
||||
funcs.addAll([SystemdUnitFunc.restart]);
|
||||
break;
|
||||
case SystemdUnitState.activating:
|
||||
funcs.addAll([SystemdUnitFunc.stop]);
|
||||
break;
|
||||
case SystemdUnitState.deactivating:
|
||||
funcs.addAll([SystemdUnitFunc.start]);
|
||||
break;
|
||||
}
|
||||
funcs.addAll([SystemdUnitFunc.status]);
|
||||
return funcs.toList();
|
||||
}
|
||||
}
|
||||
@@ -1,30 +1,7 @@
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class AppProvider extends ChangeNotifier {
|
||||
BuildContext? ctx;
|
||||
final class AppProvider {
|
||||
const AppProvider._();
|
||||
|
||||
bool isWearOS = false;
|
||||
|
||||
Future<void> init() async {
|
||||
await _initIsWearOS();
|
||||
}
|
||||
|
||||
Future<void> _initIsWearOS() async {
|
||||
if (!isAndroid) {
|
||||
isWearOS = false;
|
||||
return;
|
||||
}
|
||||
|
||||
final deviceInfo = DeviceInfoPlugin();
|
||||
final androidInfo = await deviceInfo.androidInfo;
|
||||
|
||||
const feat = 'android.hardware.type.watch';
|
||||
final hasFeat = androidInfo.systemFeatures.contains(feat);
|
||||
if (hasFeat) {
|
||||
isWearOS = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
static BuildContext? ctx;
|
||||
}
|
||||
|
||||
167
lib/data/provider/systemd.dart
Normal file
167
lib/data/provider/systemd.dart
Normal file
@@ -0,0 +1,167 @@
|
||||
import 'package:dartssh2/dartssh2.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_private_info.dart';
|
||||
import 'package:server_box/data/model/server/systemd.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
|
||||
final class SystemdProvider {
|
||||
late final SSHClient _client;
|
||||
|
||||
SystemdProvider.init(ServerPrivateInfo spi) {
|
||||
_client = Pros.server.pick(spi: spi)!.client!;
|
||||
getUnits();
|
||||
}
|
||||
|
||||
final isBusy = false.vn;
|
||||
final isRoot = false.vn;
|
||||
final units = <SystemdUnit>[].vn;
|
||||
|
||||
Future<void> getUnits() async {
|
||||
isBusy.value = true;
|
||||
|
||||
try {
|
||||
final result = await _client.runScriptIn(_getUnitsCmd);
|
||||
final units = result.split('\n');
|
||||
final isRootRaw = units.firstOrNull;
|
||||
isRoot.value = isRootRaw == '0';
|
||||
|
||||
final userUnits = <String>[];
|
||||
final systemUnits = <String>[];
|
||||
for (final unit in units.skip(1)) {
|
||||
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 result = await _client.runScriptIn(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;
|
||||
}
|
||||
}
|
||||
|
||||
String _getIniVal(String line) {
|
||||
return line.split('=').last;
|
||||
}
|
||||
|
||||
const _getUnitsCmd = '''
|
||||
# If root, get system & user units, otherwise get user units
|
||||
uid=\$(id -u)
|
||||
echo \$uid
|
||||
|
||||
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=""
|
||||
if [ "\$uid" -eq 0 ]; then
|
||||
get_files \$unit_type /etc/systemd/system
|
||||
get_files \$unit_type ~/.config/systemd/user
|
||||
else
|
||||
get_files \$unit_type ~/.config/systemd/user
|
||||
fi
|
||||
}
|
||||
|
||||
types="service socket mount timer"
|
||||
|
||||
for type in \$types; do
|
||||
get_type_files \$type
|
||||
done
|
||||
''';
|
||||
@@ -2,6 +2,6 @@
|
||||
|
||||
class BuildData {
|
||||
static const String name = "ServerBox";
|
||||
static const int build = 1057;
|
||||
static const int build = 1058;
|
||||
static const int script = 56;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import 'package:server_box/data/provider/app.dart';
|
||||
import 'package:server_box/data/provider/private_key.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/provider/sftp.dart';
|
||||
import 'package:server_box/data/provider/snippet.dart';
|
||||
|
||||
abstract final class Pros {
|
||||
static final app = AppProvider();
|
||||
static final key = PrivateKeyProvider();
|
||||
static final server = ServerProvider();
|
||||
static final sftp = SftpProvider();
|
||||
|
||||
@@ -32,7 +32,6 @@ Future<void> main() async {
|
||||
runApp(
|
||||
MultiProvider(
|
||||
providers: [
|
||||
ChangeNotifierProvider(create: (_) => Pros.app),
|
||||
ChangeNotifierProvider(create: (_) => Pros.server),
|
||||
ChangeNotifierProvider(create: (_) => Pros.snippet),
|
||||
ChangeNotifierProvider(create: (_) => Pros.key),
|
||||
@@ -97,7 +96,6 @@ Future<void> _initData() async {
|
||||
|
||||
Pros.snippet.load();
|
||||
Pros.key.load();
|
||||
await Pros.app.init();
|
||||
|
||||
if (Stores.setting.betaTest.fetch()) AppUpdate.chan = AppUpdateChan.beta;
|
||||
}
|
||||
@@ -136,6 +134,7 @@ Future<void> _doVersionRelated() async {
|
||||
// How to upgrade the data is inside each own func.
|
||||
if (curVer < newVer) {
|
||||
ServerDetailCards.autoAddNewCards(newVer);
|
||||
ServerFuncBtn.autoAddNewFuncs(newVer);
|
||||
Stores.setting.lastVer.put(newVer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import 'package:server_box/core/extension/build.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/route.dart';
|
||||
import 'package:server_box/data/model/app/tab.dart';
|
||||
import 'package:server_box/data/provider/app.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
import 'package:server_box/data/res/github_id.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
@@ -103,7 +104,7 @@ class _HomePageState extends State<HomePage>
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
Pros.app.ctx = context;
|
||||
AppProvider.ctx = context;
|
||||
|
||||
final appBar = _AppBar(
|
||||
selectIndex: _selectIndex,
|
||||
|
||||
165
lib/view/page/systemd.dart
Normal file
165
lib/view/page/systemd.dart
Normal file
@@ -0,0 +1,165 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:server_box/core/route.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/systemd.dart';
|
||||
|
||||
final class SystemdPageArgs {
|
||||
final ServerPrivateInfo spi;
|
||||
|
||||
const SystemdPageArgs({
|
||||
required this.spi,
|
||||
});
|
||||
}
|
||||
|
||||
final class SystemdPage extends StatefulWidget {
|
||||
final SystemdPageArgs args;
|
||||
|
||||
const SystemdPage({
|
||||
super.key,
|
||||
required this.args,
|
||||
});
|
||||
|
||||
static const route = AppRoute<void, SystemdPageArgs>(
|
||||
page: SystemdPage.new,
|
||||
path: '/systemd',
|
||||
);
|
||||
|
||||
@override
|
||||
State<SystemdPage> createState() => _SystemdPageState();
|
||||
}
|
||||
|
||||
final class _SystemdPageState extends State<SystemdPage> {
|
||||
late final _pro = SystemdProvider.init(widget.args.spi);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(
|
||||
title: const Text('Systemd'),
|
||||
actions: [
|
||||
Btn.icon(icon: const Icon(Icons.refresh), onTap: _pro.getUnits),
|
||||
],
|
||||
),
|
||||
body: _buildBody(),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: _pro.isBusy.listenVal(
|
||||
(isBusy) => AnimatedContainer(
|
||||
duration: Durations.medium1,
|
||||
curve: Curves.fastEaseInToSlowEaseOut,
|
||||
height: isBusy ? 50 : 0,
|
||||
child: isBusy
|
||||
? UIs.centerSizedLoadingSmall.paddingOnly(bottom: 7)
|
||||
: const SizedBox.shrink(),
|
||||
),
|
||||
),
|
||||
),
|
||||
_buildUnitList(_pro.units),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUnitList(VNode<List<SystemdUnit>> units) {
|
||||
return units.listenVal(
|
||||
(units) {
|
||||
if (units.isEmpty) {
|
||||
return SliverToBoxAdapter(
|
||||
child: ListTile(title: Text(libL10n.empty))
|
||||
.cardx
|
||||
.paddingSymmetric(horizontal: 13),
|
||||
);
|
||||
}
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final unit = units[index];
|
||||
return ListTile(
|
||||
leading: _buildScopeTag(unit.scope),
|
||||
title: unit.description != null
|
||||
? TipText(unit.name, unit.description!)
|
||||
: Text(unit.name),
|
||||
subtitle: Wrap(children: [
|
||||
_buildStateTag(unit.state),
|
||||
_buildTypeTag(unit.type),
|
||||
]).paddingOnly(top: 7),
|
||||
trailing: _buildUnitFuncs(unit),
|
||||
).cardx.paddingSymmetric(horizontal: 13);
|
||||
},
|
||||
childCount: units.length,
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUnitFuncs(SystemdUnit unit) {
|
||||
return PopupMenu(
|
||||
items: unit.availableFuncs.map(_buildUnitFuncBtn).toList(),
|
||||
onSelected: (val) async {
|
||||
final cmd = unit.getCmd(func: val, isRoot: _pro.isRoot.value);
|
||||
final sure = await context.showRoundDialog(
|
||||
title: libL10n.attention,
|
||||
child: SimpleMarkdown(data: '```shell\n$cmd\n```'),
|
||||
actions: [
|
||||
CountDownBtn(
|
||||
seconds: 1,
|
||||
onTap: () => context.pop(true),
|
||||
text: libL10n.ok,
|
||||
afterColor: Colors.red,
|
||||
),
|
||||
],
|
||||
);
|
||||
if (sure != true) return;
|
||||
|
||||
AppRoutes.ssh(spi: widget.args.spi, initCmd: cmd).go(context);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
PopupMenuEntry _buildUnitFuncBtn(SystemdUnitFunc func) {
|
||||
return PopupMenuItem<SystemdUnitFunc>(
|
||||
value: func,
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.spaceAround,
|
||||
children: [
|
||||
Icon(func.icon, size: 19),
|
||||
const SizedBox(width: 10),
|
||||
Text(func.name.upperFirst),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildScopeTag(SystemdUnitScope scope) {
|
||||
return _buildTag(scope.name.upperFirst, scope.color, true);
|
||||
}
|
||||
|
||||
Widget _buildStateTag(SystemdUnitState state) {
|
||||
return _buildTag(state.name.upperFirst, state.color);
|
||||
}
|
||||
|
||||
Widget _buildTypeTag(SystemdUnitType type) {
|
||||
return _buildTag(type.name.upperFirst);
|
||||
}
|
||||
|
||||
Widget _buildTag(String tag, [Color? color, bool noPad = false]) {
|
||||
return Container(
|
||||
decoration: BoxDecoration(
|
||||
color: color?.withOpacity(0.7) ?? UIs.halfAlpha,
|
||||
borderRadius: BorderRadius.circular(5),
|
||||
),
|
||||
child: Text(
|
||||
tag,
|
||||
style: UIs.text11Grey,
|
||||
).paddingSymmetric(horizontal: 5, vertical: 1),
|
||||
).paddingOnly(right: noPad ? 0 : 5);
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,7 @@ import 'package:server_box/data/model/app/menu/server_func.dart';
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/res/provider.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:server_box/view/page/systemd.dart';
|
||||
|
||||
import '../../core/route.dart';
|
||||
import '../../core/utils/server.dart';
|
||||
@@ -162,6 +163,12 @@ void _onTapMoreBtns(
|
||||
check: () => _checkClient(context, spi.id),
|
||||
);
|
||||
break;
|
||||
case ServerFuncBtn.systemd:
|
||||
SystemdPage.route.go(
|
||||
context,
|
||||
args: SystemdPageArgs(spi: spi),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import device_info_plus
|
||||
import dynamic_color
|
||||
import icloud_storage
|
||||
import local_auth_darwin
|
||||
@@ -20,7 +19,6 @@ import wakelock_plus
|
||||
import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
|
||||
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
|
||||
IcloudStoragePlugin.register(with: registry.registrar(forPlugin: "IcloudStoragePlugin"))
|
||||
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))
|
||||
|
||||
@@ -471,7 +471,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
|
||||
@@ -481,7 +481,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "Server Box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -608,7 +608,7 @@
|
||||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = BA88US33G6;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "Server Box";
|
||||
@@ -618,7 +618,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "Server Box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
@@ -638,7 +638,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "3rd Party Mac Developer Application";
|
||||
CODE_SIGN_STYLE = Manual;
|
||||
COMBINE_HIDPI_IMAGES = YES;
|
||||
CURRENT_PROJECT_VERSION = 1057;
|
||||
CURRENT_PROJECT_VERSION = 1058;
|
||||
DEVELOPMENT_TEAM = "";
|
||||
"DEVELOPMENT_TEAM[sdk=macosx*]" = BA88US33G6;
|
||||
INFOPLIST_FILE = Runner/Info.plist;
|
||||
@@ -649,7 +649,7 @@
|
||||
"@executable_path/../Frameworks",
|
||||
);
|
||||
MACOSX_DEPLOYMENT_TARGET = 10.15;
|
||||
MARKETING_VERSION = 1.0.1057;
|
||||
MARKETING_VERSION = 1.0.1058;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.lollipopkit.toolbox;
|
||||
PRODUCT_NAME = "Server Box";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
|
||||
24
pubspec.lock
24
pubspec.lock
@@ -273,22 +273,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
device_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: a7fd703482b391a87d60b6061d04dfdeab07826b96f9abd8f5ed98068acc0074
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.2"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: device_info_plus_platform_interface
|
||||
sha256: "282d3cf731045a2feb66abfe61bbc40870ae50a3ed10a4d3d217556c35c8c2ba"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
dio:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1559,14 +1543,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.3"
|
||||
win32_registry:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: win32_registry
|
||||
sha256: "723b7f851e5724c55409bb3d5a32b203b3afe8587eaf5dafb93a5fed8ecda0d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.4"
|
||||
window_manager:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: server_box
|
||||
description: server status & toolbox app.
|
||||
publish_to: 'none'
|
||||
version: 1.0.1057+1057
|
||||
version: 1.0.1058+1058
|
||||
|
||||
environment:
|
||||
sdk: ">=3.0.0"
|
||||
@@ -28,7 +28,6 @@ dependencies:
|
||||
wakelock_plus: ^1.2.4
|
||||
wake_on_lan: ^4.1.1+3
|
||||
flutter_adaptive_scaffold: ^0.1.10+2
|
||||
device_info_plus: ^10.1.0
|
||||
extended_image: ^8.2.1
|
||||
dartssh2:
|
||||
git:
|
||||
|
||||
Reference in New Issue
Block a user