mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-16 23:04:22 +01:00
migrate: riverpod + freezed (#870)
This commit is contained in:
@@ -2,10 +2,10 @@ import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
import 'package:server_box/data/model/server/dist.dart';
|
||||
import 'package:server_box/data/model/server/server.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
extension LogoExt on Server {
|
||||
extension LogoExt on ServerState {
|
||||
String? getLogoUrl(BuildContext context) {
|
||||
var logoUrl = spi.custom?.logoUrl ?? Stores.setting.serverLogoUrl.fetch().selfNotEmptyOrNull;
|
||||
if (logoUrl == null) {
|
||||
@@ -4,7 +4,7 @@ import 'package:server_box/core/extension/context/locale.dart';
|
||||
enum SSHErrType { unknown, connect, auth, noPrivateKey, chdir, segements, writeScript, getStatus }
|
||||
|
||||
class SSHErr extends Err<SSHErrType> {
|
||||
SSHErr({required super.type, super.message});
|
||||
const SSHErr({required super.type, super.message});
|
||||
|
||||
@override
|
||||
String? get solution => switch (type) {
|
||||
@@ -29,7 +29,7 @@ enum ContainerErrType {
|
||||
}
|
||||
|
||||
class ContainerErr extends Err<ContainerErrType> {
|
||||
ContainerErr({required super.type, super.message});
|
||||
const ContainerErr({required super.type, super.message});
|
||||
|
||||
@override
|
||||
String? get solution => null;
|
||||
@@ -38,7 +38,7 @@ class ContainerErr extends Err<ContainerErrType> {
|
||||
enum ICloudErrType { generic, notFound, multipleFiles }
|
||||
|
||||
class ICloudErr extends Err<ICloudErrType> {
|
||||
ICloudErr({required super.type, super.message});
|
||||
const ICloudErr({required super.type, super.message});
|
||||
|
||||
@override
|
||||
String? get solution => null;
|
||||
@@ -47,7 +47,7 @@ class ICloudErr extends Err<ICloudErrType> {
|
||||
enum WebdavErrType { generic, notFound }
|
||||
|
||||
class WebdavErr extends Err<WebdavErrType> {
|
||||
WebdavErr({required super.type, super.message});
|
||||
const WebdavErr({required super.type, super.message});
|
||||
|
||||
@override
|
||||
String? get solution => null;
|
||||
@@ -56,7 +56,7 @@ class WebdavErr extends Err<WebdavErrType> {
|
||||
enum PveErrType { unknown, net, loginFailed }
|
||||
|
||||
class PveErr extends Err<PveErrType> {
|
||||
PveErr({required super.type, super.message});
|
||||
const PveErr({required super.type, super.message});
|
||||
|
||||
@override
|
||||
String? get solution => null;
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'package:server_box/data/model/app/scripts/script_builders.dart';
|
||||
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
|
||||
/// Shell functions available in the ServerBox application
|
||||
enum ShellFunc {
|
||||
@@ -26,8 +25,8 @@ enum ShellFunc {
|
||||
};
|
||||
|
||||
/// Execute this shell function on the specified server
|
||||
String exec(String id, {SystemType? systemType}) {
|
||||
final scriptPath = ShellFuncManager.getScriptPath(id, systemType: systemType);
|
||||
String exec(String id, {SystemType? systemType, required String? customDir}) {
|
||||
final scriptPath = ShellFuncManager.getScriptPath(id, systemType: systemType, customDir: customDir);
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
|
||||
@@ -51,11 +50,10 @@ class ShellFuncManager {
|
||||
/// Get the script directory for the given [id].
|
||||
///
|
||||
/// Checks for custom script directory first, then falls back to default.
|
||||
static String getScriptDir(String id, {SystemType? systemType}) {
|
||||
final customScriptDir = ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
||||
static String getScriptDir(String id, {SystemType? systemType, required String? customDir}) {
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
|
||||
if (customScriptDir != null) return _normalizeDir(customScriptDir, isWindows);
|
||||
if (customDir != null) return _normalizeDir(customDir, isWindows);
|
||||
return ScriptPaths.getScriptDir(id, isWindows: isWindows);
|
||||
}
|
||||
|
||||
@@ -66,11 +64,10 @@ class ShellFuncManager {
|
||||
}
|
||||
|
||||
/// Get the full script path for the given [id]
|
||||
static String getScriptPath(String id, {SystemType? systemType}) {
|
||||
final customScriptDir = ServerProvider.pick(id: id)?.value.spi.custom?.scriptDir;
|
||||
if (customScriptDir != null) {
|
||||
static String getScriptPath(String id, {SystemType? systemType, required String? customDir}) {
|
||||
if (customDir != null) {
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final normalizedDir = _normalizeDir(customScriptDir, isWindows);
|
||||
final normalizedDir = _normalizeDir(customDir, isWindows);
|
||||
final fileName = isWindows ? ScriptConstants.scriptFileWindows : ScriptConstants.scriptFile;
|
||||
final separator = isWindows ? ScriptConstants.windowsPathSeparator : ScriptConstants.unixPathSeparator;
|
||||
return '$normalizedDir$separator$fileName';
|
||||
@@ -81,8 +78,8 @@ class ShellFuncManager {
|
||||
}
|
||||
|
||||
/// Get the installation shell command for the script
|
||||
static String getInstallShellCmd(String id, {SystemType? systemType}) {
|
||||
final scriptDir = getScriptDir(id, systemType: systemType);
|
||||
static String getInstallShellCmd(String id, {SystemType? systemType, required String? customDir}) {
|
||||
final scriptDir = getScriptDir(id, systemType: systemType, customDir: customDir);
|
||||
final isWindows = systemType == SystemType.windows;
|
||||
final normalizedDir = _normalizeDir(scriptDir, isWindows);
|
||||
final builder = ScriptBuilderFactory.getBuilder(isWindows);
|
||||
|
||||
@@ -20,11 +20,11 @@ ServerCustom _$ServerCustomFromJson(Map<String, dynamic> json) => ServerCustom(
|
||||
|
||||
Map<String, dynamic> _$ServerCustomToJson(ServerCustom instance) =>
|
||||
<String, dynamic>{
|
||||
'pveAddr': ?instance.pveAddr,
|
||||
if (instance.pveAddr case final value?) 'pveAddr': value,
|
||||
'pveIgnoreCert': instance.pveIgnoreCert,
|
||||
'cmds': ?instance.cmds,
|
||||
'preferTempDev': ?instance.preferTempDev,
|
||||
'logoUrl': ?instance.logoUrl,
|
||||
'netDev': ?instance.netDev,
|
||||
'scriptDir': ?instance.scriptDir,
|
||||
if (instance.cmds case final value?) 'cmds': value,
|
||||
if (instance.preferTempDev case final value?) 'preferTempDev': value,
|
||||
if (instance.logoUrl case final value?) 'logoUrl': value,
|
||||
if (instance.netDev case final value?) 'netDev': value,
|
||||
if (instance.scriptDir case final value?) 'scriptDir': value,
|
||||
};
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/data/model/app/scripts/cmd_types.dart';
|
||||
import 'package:server_box/data/model/server/amd.dart';
|
||||
@@ -11,25 +10,9 @@ import 'package:server_box/data/model/server/memory.dart';
|
||||
import 'package:server_box/data/model/server/net_speed.dart';
|
||||
import 'package:server_box/data/model/server/nvdia.dart';
|
||||
import 'package:server_box/data/model/server/sensors.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/model/server/temp.dart';
|
||||
|
||||
class Server {
|
||||
Spi spi;
|
||||
ServerStatus status;
|
||||
SSHClient? client;
|
||||
ServerConn conn;
|
||||
|
||||
Server(this.spi, this.status, this.conn, {this.client});
|
||||
|
||||
bool get needGenClient => conn < ServerConn.connecting;
|
||||
|
||||
bool get canViewDetails => conn == ServerConn.finished;
|
||||
|
||||
String get id => spi.id;
|
||||
}
|
||||
|
||||
class ServerStatus {
|
||||
Cpus cpu;
|
||||
Memory mem;
|
||||
|
||||
@@ -4,10 +4,8 @@ import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:server_box/data/model/app/error.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/system.dart';
|
||||
import 'package:server_box/data/model/server/wol_cfg.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/store/server.dart';
|
||||
|
||||
part 'server_private_info.freezed.dart';
|
||||
@@ -87,9 +85,6 @@ extension Spix on Spi {
|
||||
|
||||
String toJsonString() => json.encode(toJson());
|
||||
|
||||
VNode<Server>? get server => ServerProvider.pick(spi: this);
|
||||
VNode<Server>? get jumpServer => ServerProvider.pick(id: jumpId);
|
||||
|
||||
bool shouldReconnect(Spi old) {
|
||||
return user != old.user ||
|
||||
ip != old.ip ||
|
||||
|
||||
@@ -41,18 +41,19 @@ Map<String, dynamic> _$SpiToJson(_Spi instance) => <String, dynamic>{
|
||||
'ip': instance.ip,
|
||||
'port': instance.port,
|
||||
'user': instance.user,
|
||||
'pwd': ?instance.pwd,
|
||||
'pubKeyId': ?instance.keyId,
|
||||
'tags': ?instance.tags,
|
||||
'alterUrl': ?instance.alterUrl,
|
||||
if (instance.pwd case final value?) 'pwd': value,
|
||||
if (instance.keyId case final value?) 'pubKeyId': value,
|
||||
if (instance.tags case final value?) 'tags': value,
|
||||
if (instance.alterUrl case final value?) 'alterUrl': value,
|
||||
'autoConnect': instance.autoConnect,
|
||||
'jumpId': ?instance.jumpId,
|
||||
'custom': ?instance.custom,
|
||||
'wolCfg': ?instance.wolCfg,
|
||||
'envs': ?instance.envs,
|
||||
if (instance.jumpId case final value?) 'jumpId': value,
|
||||
if (instance.custom case final value?) 'custom': value,
|
||||
if (instance.wolCfg case final value?) 'wolCfg': value,
|
||||
if (instance.envs case final value?) 'envs': value,
|
||||
'id': instance.id,
|
||||
'customSystemType': ?_$SystemTypeEnumMap[instance.customSystemType],
|
||||
'disabledCmdTypes': ?instance.disabledCmdTypes,
|
||||
if (_$SystemTypeEnumMap[instance.customSystemType] case final value?)
|
||||
'customSystemType': value,
|
||||
if (instance.disabledCmdTypes case final value?) 'disabledCmdTypes': value,
|
||||
};
|
||||
|
||||
const _$SystemTypeEnumMap = {
|
||||
|
||||
@@ -16,5 +16,5 @@ Map<String, dynamic> _$WakeOnLanCfgToJson(WakeOnLanCfg instance) =>
|
||||
<String, dynamic>{
|
||||
'mac': instance.mac,
|
||||
'ip': instance.ip,
|
||||
'pwd': ?instance.pwd,
|
||||
if (instance.pwd case final value?) 'pwd': value,
|
||||
};
|
||||
|
||||
@@ -6,57 +6,20 @@ part of 'app.dart';
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
@ProviderFor(AppStates)
|
||||
const appStatesProvider = AppStatesProvider._();
|
||||
|
||||
final class AppStatesProvider extends $NotifierProvider<AppStates, AppState> {
|
||||
const AppStatesProvider._()
|
||||
: super(
|
||||
from: null,
|
||||
argument: null,
|
||||
retry: null,
|
||||
name: r'appStatesProvider',
|
||||
isAutoDispose: false,
|
||||
dependencies: null,
|
||||
$allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
@override
|
||||
String debugGetCreateSourceHash() => _$appStatesHash();
|
||||
|
||||
@$internal
|
||||
@override
|
||||
AppStates create() => AppStates();
|
||||
|
||||
/// {@macro riverpod.override_with_value}
|
||||
Override overrideWithValue(AppState value) {
|
||||
return $ProviderOverride(
|
||||
origin: this,
|
||||
providerOverride: $SyncValueProvider<AppState>(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
String _$appStatesHash() => r'ef96f10f6fff0f3dd6d3128ebf070ad79cbc8bc9';
|
||||
|
||||
abstract class _$AppStates extends $Notifier<AppState> {
|
||||
AppState build();
|
||||
@$mustCallSuper
|
||||
@override
|
||||
void runBuild() {
|
||||
final created = build();
|
||||
final ref = this.ref as $Ref<AppState, AppState>;
|
||||
final element =
|
||||
ref.element
|
||||
as $ClassProviderElement<
|
||||
AnyNotifier<AppState, AppState>,
|
||||
AppState,
|
||||
Object?,
|
||||
Object?
|
||||
>;
|
||||
element.handleValue(ref, created);
|
||||
}
|
||||
}
|
||||
/// See also [AppStates].
|
||||
@ProviderFor(AppStates)
|
||||
final appStatesProvider = NotifierProvider<AppStates, AppState>.internal(
|
||||
AppStates.new,
|
||||
name: r'appStatesProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$appStatesHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$AppStates = Notifier<AppState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'dart:convert';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.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/error.dart';
|
||||
import 'package:server_box/data/model/app/scripts/script_consts.dart';
|
||||
@@ -12,63 +14,66 @@ import 'package:server_box/data/model/container/ps.dart';
|
||||
import 'package:server_box/data/model/container/type.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
part 'container.freezed.dart';
|
||||
part 'container.g.dart';
|
||||
|
||||
final _dockerNotFound = RegExp(r"command not found|Unknown command|Command '\w+' not found");
|
||||
|
||||
class ContainerProvider extends ChangeNotifier {
|
||||
final SSHClient? client;
|
||||
final String userName;
|
||||
final String hostId;
|
||||
final BuildContext context;
|
||||
List<ContainerPs>? items;
|
||||
List<ContainerImg>? images;
|
||||
String? version;
|
||||
ContainerErr? error;
|
||||
String? runLog;
|
||||
ContainerType type;
|
||||
var sudoCompleter = Completer<bool>();
|
||||
bool isBusy = false;
|
||||
@freezed
|
||||
abstract class ContainerState with _$ContainerState {
|
||||
const factory ContainerState({
|
||||
@Default(null) List<ContainerPs>? items,
|
||||
@Default(null) List<ContainerImg>? images,
|
||||
@Default(null) String? version,
|
||||
@Default(null) ContainerErr? error,
|
||||
@Default(null) String? runLog,
|
||||
@Default(ContainerType.docker) ContainerType type,
|
||||
@Default(false) bool isBusy,
|
||||
}) = _ContainerState;
|
||||
}
|
||||
|
||||
ContainerProvider({
|
||||
required this.client,
|
||||
required this.userName,
|
||||
required this.hostId,
|
||||
required this.context,
|
||||
}) : type = Stores.container.getType(hostId) {
|
||||
refresh();
|
||||
@riverpod
|
||||
class ContainerNotifier extends _$ContainerNotifier {
|
||||
var sudoCompleter = Completer<bool>();
|
||||
|
||||
@override
|
||||
ContainerState build(SSHClient? client, String userName, String hostId, BuildContext context) {
|
||||
this.client = client;
|
||||
this.userName = userName;
|
||||
this.hostId = hostId;
|
||||
this.context = context;
|
||||
|
||||
final type = Stores.container.getType(hostId);
|
||||
final initialState = ContainerState(type: type);
|
||||
|
||||
// Async initialization
|
||||
Future.microtask(() => refresh());
|
||||
|
||||
return initialState;
|
||||
}
|
||||
|
||||
Future<void> setType(ContainerType type) async {
|
||||
this.type = type;
|
||||
state = state.copyWith(
|
||||
type: type,
|
||||
error: null,
|
||||
runLog: null,
|
||||
items: null,
|
||||
images: null,
|
||||
version: null,
|
||||
);
|
||||
Stores.container.setType(type, hostId);
|
||||
error = runLog = items = images = version = null;
|
||||
sudoCompleter = Completer<bool>();
|
||||
notifyListeners();
|
||||
await refresh();
|
||||
}
|
||||
|
||||
// Future<bool> _checkDockerInstalled(SSHClient client) async {
|
||||
// final session = await client.execute("docker");
|
||||
// await session.done;
|
||||
// // debugPrint('docker code: ${session.exitCode}');
|
||||
// return session.exitCode == 0;
|
||||
// }
|
||||
|
||||
// String _removeSudoPrompts(String value) {
|
||||
// final regex = RegExp(r"\[sudo\] password for \w+:");
|
||||
// if (value.startsWith(regex)) {
|
||||
// return value.replaceFirstMapped(regex, (match) => "");
|
||||
// }
|
||||
// return value;
|
||||
// }
|
||||
|
||||
void _requiresSudo() async {
|
||||
/// Podman is rootless
|
||||
if (type == ContainerType.podman) return sudoCompleter.complete(false);
|
||||
if (state.type == ContainerType.podman) return sudoCompleter.complete(false);
|
||||
if (!Stores.setting.containerTrySudo.fetch()) {
|
||||
return sudoCompleter.complete(false);
|
||||
}
|
||||
|
||||
final res = await client?.run(_wrap(ContainerCmdType.images.exec(type)));
|
||||
final res = await client?.run(_wrap(ContainerCmdType.images.exec(state.type)));
|
||||
if (res?.string.toLowerCase().contains('permission denied') ?? false) {
|
||||
return sudoCompleter.complete(true);
|
||||
}
|
||||
@@ -76,8 +81,8 @@ class ContainerProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<void> refresh({bool isAuto = false}) async {
|
||||
if (isBusy) return;
|
||||
isBusy = true;
|
||||
if (state.isBusy) return;
|
||||
state = state.copyWith(isBusy: true);
|
||||
|
||||
if (!sudoCompleter.isCompleted) _requiresSudo();
|
||||
|
||||
@@ -85,11 +90,14 @@ class ContainerProvider extends ChangeNotifier {
|
||||
|
||||
/// If sudo is required and auto refresh is enabled, skip the refresh.
|
||||
/// Or this will ask for pwd again and again.
|
||||
if (sudo && isAuto) return;
|
||||
if (sudo && isAuto) {
|
||||
state = state.copyWith(isBusy: false);
|
||||
return;
|
||||
}
|
||||
final includeStats = Stores.setting.containerParseStat.fetch();
|
||||
|
||||
var raw = '';
|
||||
final cmd = _wrap(ContainerCmdType.execAll(type, sudo: sudo, includeStats: includeStats));
|
||||
final cmd = _wrap(ContainerCmdType.execAll(state.type, sudo: sudo, includeStats: includeStats));
|
||||
final code = await client?.execWithPwd(
|
||||
cmd,
|
||||
context: context,
|
||||
@@ -97,75 +105,79 @@ class ContainerProvider extends ChangeNotifier {
|
||||
id: hostId,
|
||||
);
|
||||
|
||||
isBusy = false;
|
||||
state = state.copyWith(isBusy: false);
|
||||
|
||||
if (!context.mounted) return;
|
||||
|
||||
/// Code 127 means command not found
|
||||
if (code == 127 || raw.contains(_dockerNotFound)) {
|
||||
error = ContainerErr(type: ContainerErrType.notInstalled);
|
||||
notifyListeners();
|
||||
state = state.copyWith(error: ContainerErr(type: ContainerErrType.notInstalled));
|
||||
return;
|
||||
}
|
||||
|
||||
// Check result segments count
|
||||
final segments = raw.split(ScriptConstants.separator);
|
||||
if (segments.length != ContainerCmdType.values.length) {
|
||||
error = ContainerErr(
|
||||
type: ContainerErrType.segmentsNotMatch,
|
||||
message: 'Container segments: ${segments.length}',
|
||||
state = state.copyWith(
|
||||
error: ContainerErr(
|
||||
type: ContainerErrType.segmentsNotMatch,
|
||||
message: 'Container segments: ${segments.length}',
|
||||
),
|
||||
);
|
||||
Loggers.app.warning('Container segments: ${segments.length}\n$raw');
|
||||
notifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
// Parse version
|
||||
final verRaw = ContainerCmdType.version.find(segments);
|
||||
try {
|
||||
version = json.decode(verRaw)['Client']['Version'];
|
||||
final version = json.decode(verRaw)['Client']['Version'];
|
||||
state = state.copyWith(version: version, error: null);
|
||||
} catch (e, trace) {
|
||||
error = ContainerErr(type: ContainerErrType.invalidVersion, message: '$e');
|
||||
state = state.copyWith(
|
||||
error: ContainerErr(type: ContainerErrType.invalidVersion, message: '$e'),
|
||||
);
|
||||
Loggers.app.warning('Container version failed', e, trace);
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Parse ps
|
||||
final psRaw = ContainerCmdType.ps.find(segments);
|
||||
try {
|
||||
final lines = psRaw.split('\n');
|
||||
if (type == ContainerType.docker) {
|
||||
if (state.type == ContainerType.docker) {
|
||||
/// Due to the fetched data is not in json format, skip table header
|
||||
lines.removeWhere((element) => element.contains('CONTAINER ID'));
|
||||
}
|
||||
lines.removeWhere((element) => element.isEmpty);
|
||||
items = lines.map((e) => ContainerPs.fromRaw(e, type)).toList();
|
||||
final items = lines.map((e) => ContainerPs.fromRaw(e, state.type)).toList();
|
||||
state = state.copyWith(items: items);
|
||||
} catch (e, trace) {
|
||||
error = ContainerErr(type: ContainerErrType.parsePs, message: '$e');
|
||||
state = state.copyWith(
|
||||
error: ContainerErr(type: ContainerErrType.parsePs, message: '$e'),
|
||||
);
|
||||
Loggers.app.warning('Container ps failed', e, trace);
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Parse images
|
||||
final imageRaw = ContainerCmdType.images.find(segments).trim();
|
||||
final isEntireJson = imageRaw.startsWith('[') && imageRaw.endsWith(']');
|
||||
try {
|
||||
List<ContainerImg> images;
|
||||
if (isEntireJson) {
|
||||
images = (json.decode(imageRaw) as List)
|
||||
.map((e) => ContainerImg.fromRawJson(json.encode(e), type))
|
||||
.map((e) => ContainerImg.fromRawJson(json.encode(e), state.type))
|
||||
.toList();
|
||||
} else {
|
||||
final lines = imageRaw.split('\n');
|
||||
lines.removeWhere((element) => element.isEmpty);
|
||||
images = lines.map((e) => ContainerImg.fromRawJson(e, type)).toList();
|
||||
images = lines.map((e) => ContainerImg.fromRawJson(e, state.type)).toList();
|
||||
}
|
||||
state = state.copyWith(images: images);
|
||||
} catch (e, trace) {
|
||||
error = ContainerErr(type: ContainerErrType.parseImages, message: '$e');
|
||||
state = state.copyWith(
|
||||
error: ContainerErr(type: ContainerErrType.parseImages, message: '$e'),
|
||||
);
|
||||
Loggers.app.warning('Container images failed', e, trace);
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
// Parse stats
|
||||
@@ -173,7 +185,7 @@ class ContainerProvider extends ChangeNotifier {
|
||||
try {
|
||||
final statsLines = statsRaw.split('\n');
|
||||
statsLines.removeWhere((element) => element.isEmpty);
|
||||
for (var item in items!) {
|
||||
for (var item in state.items!) {
|
||||
final id = item.id;
|
||||
if (id == null) continue;
|
||||
final statsLine = statsLines.firstWhereOrNull(
|
||||
@@ -185,10 +197,10 @@ class ContainerProvider extends ChangeNotifier {
|
||||
item.parseStats(statsLine);
|
||||
}
|
||||
} catch (e, trace) {
|
||||
error = ContainerErr(type: ContainerErrType.parseStats, message: '$e');
|
||||
state = state.copyWith(
|
||||
error: ContainerErr(type: ContainerErrType.parseStats, message: '$e'),
|
||||
);
|
||||
Loggers.app.warning('Parse docker stats: $statsRaw', e, trace);
|
||||
} finally {
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,25 +235,23 @@ class ContainerProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future<ContainerErr?> run(String cmd, {bool autoRefresh = true}) async {
|
||||
cmd = switch (type) {
|
||||
cmd = switch (state.type) {
|
||||
ContainerType.docker => 'docker $cmd',
|
||||
ContainerType.podman => 'podman $cmd',
|
||||
};
|
||||
|
||||
runLog = '';
|
||||
state = state.copyWith(runLog: '');
|
||||
final errs = <String>[];
|
||||
final code = await client?.execWithPwd(
|
||||
_wrap((await sudoCompleter.future) ? 'sudo -S $cmd' : cmd),
|
||||
context: context,
|
||||
onStdout: (data, _) {
|
||||
runLog = '$runLog$data';
|
||||
notifyListeners();
|
||||
state = state.copyWith(runLog: '${state.runLog}$data');
|
||||
},
|
||||
onStderr: (data, _) => errs.add(data),
|
||||
id: hostId,
|
||||
);
|
||||
runLog = null;
|
||||
notifyListeners();
|
||||
state = state.copyWith(runLog: null);
|
||||
|
||||
if (code != 0) {
|
||||
return ContainerErr(type: ContainerErrType.unknown, message: errs.join('\n').trim());
|
||||
@@ -262,6 +272,7 @@ class ContainerProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const _jsonFmt = '--format "{{json .}}"';
|
||||
|
||||
enum ContainerCmdType {
|
||||
|
||||
305
lib/data/provider/container.freezed.dart
Normal file
305
lib/data/provider/container.freezed.dart
Normal file
@@ -0,0 +1,305 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'container.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$ContainerState {
|
||||
|
||||
List<ContainerPs>? get items; List<ContainerImg>? get images; String? get version; ContainerErr? get error; String? get runLog; ContainerType get type; bool get isBusy;
|
||||
/// Create a copy of ContainerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ContainerStateCopyWith<ContainerState> get copyWith => _$ContainerStateCopyWithImpl<ContainerState>(this as ContainerState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ContainerState&&const DeepCollectionEquality().equals(other.items, items)&&const DeepCollectionEquality().equals(other.images, images)&&(identical(other.version, version) || other.version == version)&&(identical(other.error, error) || other.error == error)&&(identical(other.runLog, runLog) || other.runLog == runLog)&&(identical(other.type, type) || other.type == type)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(items),const DeepCollectionEquality().hash(images),version,error,runLog,type,isBusy);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ContainerState(items: $items, images: $images, version: $version, error: $error, runLog: $runLog, type: $type, isBusy: $isBusy)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ContainerStateCopyWith<$Res> {
|
||||
factory $ContainerStateCopyWith(ContainerState value, $Res Function(ContainerState) _then) = _$ContainerStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ContainerStateCopyWithImpl<$Res>
|
||||
implements $ContainerStateCopyWith<$Res> {
|
||||
_$ContainerStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ContainerState _self;
|
||||
final $Res Function(ContainerState) _then;
|
||||
|
||||
/// Create a copy of ContainerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? items = freezed,Object? images = freezed,Object? version = freezed,Object? error = freezed,Object? runLog = freezed,Object? type = null,Object? isBusy = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
items: freezed == items ? _self.items : items // ignore: cast_nullable_to_non_nullable
|
||||
as List<ContainerPs>?,images: freezed == images ? _self.images : images // ignore: cast_nullable_to_non_nullable
|
||||
as List<ContainerImg>?,version: freezed == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
|
||||
as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as ContainerErr?,runLog: freezed == runLog ? _self.runLog : runLog // ignore: cast_nullable_to_non_nullable
|
||||
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as ContainerType,isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ContainerState].
|
||||
extension ContainerStatePatterns on ContainerState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ContainerState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ContainerState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ContainerState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ContainerState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ContainerState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ContainerState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ContainerState() when $default != null:
|
||||
return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,_that.type,_that.isBusy);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ContainerState():
|
||||
return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,_that.type,_that.isBusy);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ContainerState() when $default != null:
|
||||
return $default(_that.items,_that.images,_that.version,_that.error,_that.runLog,_that.type,_that.isBusy);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ContainerState implements ContainerState {
|
||||
const _ContainerState({final List<ContainerPs>? items = null, final List<ContainerImg>? images = null, this.version = null, this.error = null, this.runLog = null, this.type = ContainerType.docker, this.isBusy = false}): _items = items,_images = images;
|
||||
|
||||
|
||||
final List<ContainerPs>? _items;
|
||||
@override@JsonKey() List<ContainerPs>? get items {
|
||||
final value = _items;
|
||||
if (value == null) return null;
|
||||
if (_items is EqualUnmodifiableListView) return _items;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
final List<ContainerImg>? _images;
|
||||
@override@JsonKey() List<ContainerImg>? get images {
|
||||
final value = _images;
|
||||
if (value == null) return null;
|
||||
if (_images is EqualUnmodifiableListView) return _images;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(value);
|
||||
}
|
||||
|
||||
@override@JsonKey() final String? version;
|
||||
@override@JsonKey() final ContainerErr? error;
|
||||
@override@JsonKey() final String? runLog;
|
||||
@override@JsonKey() final ContainerType type;
|
||||
@override@JsonKey() final bool isBusy;
|
||||
|
||||
/// Create a copy of ContainerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ContainerStateCopyWith<_ContainerState> get copyWith => __$ContainerStateCopyWithImpl<_ContainerState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ContainerState&&const DeepCollectionEquality().equals(other._items, _items)&&const DeepCollectionEquality().equals(other._images, _images)&&(identical(other.version, version) || other.version == version)&&(identical(other.error, error) || other.error == error)&&(identical(other.runLog, runLog) || other.runLog == runLog)&&(identical(other.type, type) || other.type == type)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_items),const DeepCollectionEquality().hash(_images),version,error,runLog,type,isBusy);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ContainerState(items: $items, images: $images, version: $version, error: $error, runLog: $runLog, type: $type, isBusy: $isBusy)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ContainerStateCopyWith<$Res> implements $ContainerStateCopyWith<$Res> {
|
||||
factory _$ContainerStateCopyWith(_ContainerState value, $Res Function(_ContainerState) _then) = __$ContainerStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<ContainerPs>? items, List<ContainerImg>? images, String? version, ContainerErr? error, String? runLog, ContainerType type, bool isBusy
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ContainerStateCopyWithImpl<$Res>
|
||||
implements _$ContainerStateCopyWith<$Res> {
|
||||
__$ContainerStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ContainerState _self;
|
||||
final $Res Function(_ContainerState) _then;
|
||||
|
||||
/// Create a copy of ContainerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? items = freezed,Object? images = freezed,Object? version = freezed,Object? error = freezed,Object? runLog = freezed,Object? type = null,Object? isBusy = null,}) {
|
||||
return _then(_ContainerState(
|
||||
items: freezed == items ? _self._items : items // ignore: cast_nullable_to_non_nullable
|
||||
as List<ContainerPs>?,images: freezed == images ? _self._images : images // ignore: cast_nullable_to_non_nullable
|
||||
as List<ContainerImg>?,version: freezed == version ? _self.version : version // ignore: cast_nullable_to_non_nullable
|
||||
as String?,error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as ContainerErr?,runLog: freezed == runLog ? _self.runLog : runLog // ignore: cast_nullable_to_non_nullable
|
||||
as String?,type: null == type ? _self.type : type // ignore: cast_nullable_to_non_nullable
|
||||
as ContainerType,isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
228
lib/data/provider/container.g.dart
Normal file
228
lib/data/provider/container.g.dart
Normal file
@@ -0,0 +1,228 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'container.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$containerNotifierHash() => r'db8f8a6b6071b7b33fbf79128dfed408a5b9fdad';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$ContainerNotifier
|
||||
extends BuildlessAutoDisposeNotifier<ContainerState> {
|
||||
late final SSHClient? client;
|
||||
late final String userName;
|
||||
late final String hostId;
|
||||
late final BuildContext context;
|
||||
|
||||
ContainerState build(
|
||||
SSHClient? client,
|
||||
String userName,
|
||||
String hostId,
|
||||
BuildContext context,
|
||||
);
|
||||
}
|
||||
|
||||
/// See also [ContainerNotifier].
|
||||
@ProviderFor(ContainerNotifier)
|
||||
const containerNotifierProvider = ContainerNotifierFamily();
|
||||
|
||||
/// See also [ContainerNotifier].
|
||||
class ContainerNotifierFamily extends Family<ContainerState> {
|
||||
/// See also [ContainerNotifier].
|
||||
const ContainerNotifierFamily();
|
||||
|
||||
/// See also [ContainerNotifier].
|
||||
ContainerNotifierProvider call(
|
||||
SSHClient? client,
|
||||
String userName,
|
||||
String hostId,
|
||||
BuildContext context,
|
||||
) {
|
||||
return ContainerNotifierProvider(client, userName, hostId, context);
|
||||
}
|
||||
|
||||
@override
|
||||
ContainerNotifierProvider getProviderOverride(
|
||||
covariant ContainerNotifierProvider provider,
|
||||
) {
|
||||
return call(
|
||||
provider.client,
|
||||
provider.userName,
|
||||
provider.hostId,
|
||||
provider.context,
|
||||
);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'containerNotifierProvider';
|
||||
}
|
||||
|
||||
/// See also [ContainerNotifier].
|
||||
class ContainerNotifierProvider
|
||||
extends AutoDisposeNotifierProviderImpl<ContainerNotifier, ContainerState> {
|
||||
/// See also [ContainerNotifier].
|
||||
ContainerNotifierProvider(
|
||||
SSHClient? client,
|
||||
String userName,
|
||||
String hostId,
|
||||
BuildContext context,
|
||||
) : this._internal(
|
||||
() => ContainerNotifier()
|
||||
..client = client
|
||||
..userName = userName
|
||||
..hostId = hostId
|
||||
..context = context,
|
||||
from: containerNotifierProvider,
|
||||
name: r'containerNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$containerNotifierHash,
|
||||
dependencies: ContainerNotifierFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
ContainerNotifierFamily._allTransitiveDependencies,
|
||||
client: client,
|
||||
userName: userName,
|
||||
hostId: hostId,
|
||||
context: context,
|
||||
);
|
||||
|
||||
ContainerNotifierProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.client,
|
||||
required this.userName,
|
||||
required this.hostId,
|
||||
required this.context,
|
||||
}) : super.internal();
|
||||
|
||||
final SSHClient? client;
|
||||
final String userName;
|
||||
final String hostId;
|
||||
final BuildContext context;
|
||||
|
||||
@override
|
||||
ContainerState runNotifierBuild(covariant ContainerNotifier notifier) {
|
||||
return notifier.build(client, userName, hostId, context);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(ContainerNotifier Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: ContainerNotifierProvider._internal(
|
||||
() => create()
|
||||
..client = client
|
||||
..userName = userName
|
||||
..hostId = hostId
|
||||
..context = context,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
client: client,
|
||||
userName: userName,
|
||||
hostId: hostId,
|
||||
context: context,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<ContainerNotifier, ContainerState>
|
||||
createElement() {
|
||||
return _ContainerNotifierProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is ContainerNotifierProvider &&
|
||||
other.client == client &&
|
||||
other.userName == userName &&
|
||||
other.hostId == hostId &&
|
||||
other.context == context;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, client.hashCode);
|
||||
hash = _SystemHash.combine(hash, userName.hashCode);
|
||||
hash = _SystemHash.combine(hash, hostId.hashCode);
|
||||
hash = _SystemHash.combine(hash, context.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin ContainerNotifierRef on AutoDisposeNotifierProviderRef<ContainerState> {
|
||||
/// The parameter `client` of this provider.
|
||||
SSHClient? get client;
|
||||
|
||||
/// The parameter `userName` of this provider.
|
||||
String get userName;
|
||||
|
||||
/// The parameter `hostId` of this provider.
|
||||
String get hostId;
|
||||
|
||||
/// The parameter `context` of this provider.
|
||||
BuildContext get context;
|
||||
}
|
||||
|
||||
class _ContainerNotifierProviderElement
|
||||
extends
|
||||
AutoDisposeNotifierProviderElement<ContainerNotifier, ContainerState>
|
||||
with ContainerNotifierRef {
|
||||
_ContainerNotifierProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
SSHClient? get client => (origin as ContainerNotifierProvider).client;
|
||||
@override
|
||||
String get userName => (origin as ContainerNotifierProvider).userName;
|
||||
@override
|
||||
String get hostId => (origin as ContainerNotifierProvider).hostId;
|
||||
@override
|
||||
BuildContext get context => (origin as ContainerNotifierProvider).context;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -1,45 +1,53 @@
|
||||
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/sync.dart';
|
||||
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class PrivateKeyProvider extends Provider {
|
||||
const PrivateKeyProvider._();
|
||||
static const instance = PrivateKeyProvider._();
|
||||
part 'private_key.freezed.dart';
|
||||
part 'private_key.g.dart';
|
||||
|
||||
static final pkis = <PrivateKeyInfo>[].vn;
|
||||
@freezed
|
||||
abstract class PrivateKeyState with _$PrivateKeyState {
|
||||
const factory PrivateKeyState({
|
||||
@Default(<PrivateKeyInfo>[]) List<PrivateKeyInfo> keys,
|
||||
}) = _PrivateKeyState;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class PrivateKeyNotifier extends _$PrivateKeyNotifier {
|
||||
@override
|
||||
void load() {
|
||||
super.load();
|
||||
pkis.value = Stores.key.fetch();
|
||||
PrivateKeyState build() {
|
||||
final keys = Stores.key.fetch();
|
||||
return PrivateKeyState(keys: keys);
|
||||
}
|
||||
|
||||
static void add(PrivateKeyInfo info) {
|
||||
pkis.value.add(info);
|
||||
pkis.notify();
|
||||
void add(PrivateKeyInfo info) {
|
||||
final newKeys = [...state.keys, info];
|
||||
state = state.copyWith(keys: newKeys);
|
||||
Stores.key.put(info);
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void delete(PrivateKeyInfo info) {
|
||||
pkis.value.removeWhere((e) => e.id == info.id);
|
||||
pkis.notify();
|
||||
void delete(PrivateKeyInfo info) {
|
||||
final newKeys = state.keys.where((e) => e.id != info.id).toList();
|
||||
state = state.copyWith(keys: newKeys);
|
||||
Stores.key.delete(info);
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void update(PrivateKeyInfo old, PrivateKeyInfo newInfo) {
|
||||
final idx = pkis.value.indexWhere((e) => e.id == old.id);
|
||||
void update(PrivateKeyInfo old, PrivateKeyInfo newInfo) {
|
||||
final keys = [...state.keys];
|
||||
final idx = keys.indexWhere((e) => e.id == old.id);
|
||||
if (idx == -1) {
|
||||
pkis.value.add(newInfo);
|
||||
keys.add(newInfo);
|
||||
Stores.key.put(newInfo);
|
||||
Stores.key.delete(old);
|
||||
} else {
|
||||
pkis.value[idx] = newInfo;
|
||||
keys[idx] = newInfo;
|
||||
Stores.key.put(newInfo);
|
||||
}
|
||||
pkis.notify();
|
||||
state = state.copyWith(keys: keys);
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
}
|
||||
|
||||
277
lib/data/provider/private_key.freezed.dart
Normal file
277
lib/data/provider/private_key.freezed.dart
Normal file
@@ -0,0 +1,277 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'private_key.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$PrivateKeyState {
|
||||
|
||||
List<PrivateKeyInfo> get keys;
|
||||
/// Create a copy of PrivateKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PrivateKeyStateCopyWith<PrivateKeyState> get copyWith => _$PrivateKeyStateCopyWithImpl<PrivateKeyState>(this as PrivateKeyState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PrivateKeyState&&const DeepCollectionEquality().equals(other.keys, keys));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(keys));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PrivateKeyState(keys: $keys)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PrivateKeyStateCopyWith<$Res> {
|
||||
factory $PrivateKeyStateCopyWith(PrivateKeyState value, $Res Function(PrivateKeyState) _then) = _$PrivateKeyStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<PrivateKeyInfo> keys
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PrivateKeyStateCopyWithImpl<$Res>
|
||||
implements $PrivateKeyStateCopyWith<$Res> {
|
||||
_$PrivateKeyStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PrivateKeyState _self;
|
||||
final $Res Function(PrivateKeyState) _then;
|
||||
|
||||
/// Create a copy of PrivateKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? keys = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
keys: null == keys ? _self.keys : keys // ignore: cast_nullable_to_non_nullable
|
||||
as List<PrivateKeyInfo>,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PrivateKeyState].
|
||||
extension PrivateKeyStatePatterns on PrivateKeyState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PrivateKeyState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PrivateKeyState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PrivateKeyState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PrivateKeyState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PrivateKeyState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PrivateKeyState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<PrivateKeyInfo> keys)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PrivateKeyState() when $default != null:
|
||||
return $default(_that.keys);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<PrivateKeyInfo> keys) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PrivateKeyState():
|
||||
return $default(_that.keys);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<PrivateKeyInfo> keys)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PrivateKeyState() when $default != null:
|
||||
return $default(_that.keys);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _PrivateKeyState implements PrivateKeyState {
|
||||
const _PrivateKeyState({final List<PrivateKeyInfo> keys = const <PrivateKeyInfo>[]}): _keys = keys;
|
||||
|
||||
|
||||
final List<PrivateKeyInfo> _keys;
|
||||
@override@JsonKey() List<PrivateKeyInfo> get keys {
|
||||
if (_keys is EqualUnmodifiableListView) return _keys;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_keys);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of PrivateKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PrivateKeyStateCopyWith<_PrivateKeyState> get copyWith => __$PrivateKeyStateCopyWithImpl<_PrivateKeyState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PrivateKeyState&&const DeepCollectionEquality().equals(other._keys, _keys));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_keys));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PrivateKeyState(keys: $keys)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PrivateKeyStateCopyWith<$Res> implements $PrivateKeyStateCopyWith<$Res> {
|
||||
factory _$PrivateKeyStateCopyWith(_PrivateKeyState value, $Res Function(_PrivateKeyState) _then) = __$PrivateKeyStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<PrivateKeyInfo> keys
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PrivateKeyStateCopyWithImpl<$Res>
|
||||
implements _$PrivateKeyStateCopyWith<$Res> {
|
||||
__$PrivateKeyStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PrivateKeyState _self;
|
||||
final $Res Function(_PrivateKeyState) _then;
|
||||
|
||||
/// Create a copy of PrivateKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? keys = null,}) {
|
||||
return _then(_PrivateKeyState(
|
||||
keys: null == keys ? _self._keys : keys // ignore: cast_nullable_to_non_nullable
|
||||
as List<PrivateKeyInfo>,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
27
lib/data/provider/private_key.g.dart
Normal file
27
lib/data/provider/private_key.g.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'private_key.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$privateKeyNotifierHash() =>
|
||||
r'404836a4409f64d305c1e22f4a57b52985a57b68';
|
||||
|
||||
/// See also [PrivateKeyNotifier].
|
||||
@ProviderFor(PrivateKeyNotifier)
|
||||
final privateKeyNotifierProvider =
|
||||
NotifierProvider<PrivateKeyNotifier, PrivateKeyState>.internal(
|
||||
PrivateKeyNotifier.new,
|
||||
name: r'privateKeyNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$privateKeyNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$PrivateKeyNotifier = Notifier<PrivateKeyState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
82
lib/data/provider/providers.dart
Normal file
82
lib/data/provider/providers.dart
Normal file
@@ -0,0 +1,82 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
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';
|
||||
|
||||
/// !library;
|
||||
/// ref.useNotifier, ref.readProvider, ref.watchProvider
|
||||
///
|
||||
/// Usage:
|
||||
/// - `providers.read.server` -> `ref.read(serversNotifierProvider)`
|
||||
/// - `providers.use.snippet` -> `ref.read(snippetsNotifierProvider.notifier)`
|
||||
|
||||
extension RiverpodNotifiers on ConsumerState {
|
||||
T useNotifier<T extends Notifier<Object?>>(NotifierProvider<T, Object?> provider) {
|
||||
return ref.read(provider.notifier);
|
||||
}
|
||||
|
||||
T readProvider<T>(ProviderBase<T> provider) {
|
||||
return ref.read(provider);
|
||||
}
|
||||
|
||||
T watchProvider<T>(ProviderBase<T> provider) {
|
||||
return ref.watch(provider);
|
||||
}
|
||||
|
||||
MyProviders get providers => MyProviders(ref);
|
||||
}
|
||||
|
||||
final class MyProviders {
|
||||
final WidgetRef ref;
|
||||
const MyProviders(this.ref);
|
||||
|
||||
ReadMyProvider get read => ReadMyProvider(ref);
|
||||
WatchMyProvider get watch => WatchMyProvider(ref);
|
||||
UseNotifierMyProvider get use => UseNotifierMyProvider(ref);
|
||||
}
|
||||
|
||||
final class ReadMyProvider {
|
||||
final WidgetRef ref;
|
||||
const ReadMyProvider(this.ref);
|
||||
|
||||
T call<T>(ProviderBase<T> provider) => ref.read(provider);
|
||||
|
||||
// Specific provider getters
|
||||
ServersState get server => ref.read(serverNotifierProvider);
|
||||
SnippetState get snippet => ref.read(snippetNotifierProvider);
|
||||
AppState get app => ref.read(appStatesProvider);
|
||||
PrivateKeyState get privateKey => ref.read(privateKeyNotifierProvider);
|
||||
SftpState get sftp => ref.read(sftpNotifierProvider);
|
||||
}
|
||||
|
||||
final class WatchMyProvider {
|
||||
final WidgetRef ref;
|
||||
const WatchMyProvider(this.ref);
|
||||
|
||||
T call<T>(ProviderBase<T> provider) => ref.watch(provider);
|
||||
|
||||
// Specific provider getters
|
||||
ServersState get server => ref.watch(serverNotifierProvider);
|
||||
SnippetState get snippet => ref.watch(snippetNotifierProvider);
|
||||
AppState get app => ref.watch(appStatesProvider);
|
||||
PrivateKeyState get privateKey => ref.watch(privateKeyNotifierProvider);
|
||||
SftpState get sftp => ref.watch(sftpNotifierProvider);
|
||||
}
|
||||
|
||||
final class UseNotifierMyProvider {
|
||||
final WidgetRef ref;
|
||||
const UseNotifierMyProvider(this.ref);
|
||||
|
||||
T call<T extends Notifier<Object?>>(NotifierProvider<T, Object?> provider) =>
|
||||
ref.read(provider.notifier);
|
||||
|
||||
// Specific provider notifier getters
|
||||
ServerNotifier get server => ref.read(serverNotifierProvider.notifier);
|
||||
SnippetNotifier get snippet => ref.read(snippetNotifierProvider.notifier);
|
||||
AppStates get app => ref.read(appStatesProvider.notifier);
|
||||
PrivateKeyNotifier get privateKey => ref.read(privateKeyNotifierProvider.notifier);
|
||||
SftpNotifier get sftp => ref.read(sftpNotifierProvider.notifier);
|
||||
}
|
||||
@@ -7,71 +7,90 @@ import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
import 'package:server_box/data/model/server/pve.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
|
||||
part 'pve.freezed.dart';
|
||||
part 'pve.g.dart';
|
||||
|
||||
typedef PveCtrlFunc = Future<bool> Function(String node, String id);
|
||||
|
||||
final class PveProvider extends ChangeNotifier {
|
||||
final Spi spi;
|
||||
@freezed
|
||||
abstract class PveState with _$PveState {
|
||||
const factory PveState({
|
||||
@Default(null) PveErr? error,
|
||||
@Default(null) PveRes? data,
|
||||
@Default(null) String? release,
|
||||
@Default(false) bool isBusy,
|
||||
@Default(false) bool isConnected,
|
||||
}) = _PveState;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class PveNotifier extends _$PveNotifier {
|
||||
late final Spi spi;
|
||||
late String addr;
|
||||
late final SSHClient _client;
|
||||
late final ServerSocket _serverSocket;
|
||||
final List<SSHForwardChannel> _forwards = [];
|
||||
int _localPort = 0;
|
||||
late final Dio session;
|
||||
late final bool _ignoreCert;
|
||||
|
||||
PveProvider({required this.spi}) {
|
||||
final client = spi.server?.value.client;
|
||||
@override
|
||||
PveState build(Spi spiParam) {
|
||||
spi = spiParam;
|
||||
final serverState = ref.watch(individualServerNotifierProvider(spi.id));
|
||||
final client = serverState.client;
|
||||
if (client == null) {
|
||||
throw Exception('Server client is null');
|
||||
return const PveState(error: PveErr(type: PveErrType.net, message: 'Server client is null'));
|
||||
}
|
||||
_client = client;
|
||||
final addr = spi.custom?.pveAddr;
|
||||
if (addr == null) {
|
||||
err.value = 'PVE address is null';
|
||||
return;
|
||||
return PveState(error: PveErr(type: PveErrType.net, message: 'PVE address is null'));
|
||||
}
|
||||
this.addr = addr;
|
||||
_init();
|
||||
_ignoreCert = spi.custom?.pveIgnoreCert ?? false;
|
||||
_initSession();
|
||||
// Async initialization
|
||||
Future.microtask(() => _init());
|
||||
return const PveState();
|
||||
}
|
||||
|
||||
final err = ValueNotifier<String?>(null);
|
||||
final connected = Completer<void>();
|
||||
void _initSession() {
|
||||
session = Dio()
|
||||
..httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.connectionFactory = cf;
|
||||
if (_ignoreCert) {
|
||||
client.badCertificateCallback = (_, _, _) => true;
|
||||
}
|
||||
return client;
|
||||
},
|
||||
validateCertificate: _ignoreCert ? (_, _, _) => true : null,
|
||||
);
|
||||
}
|
||||
|
||||
late final _ignoreCert = spi.custom?.pveIgnoreCert ?? false;
|
||||
late final session = Dio()
|
||||
..httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.connectionFactory = cf;
|
||||
if (_ignoreCert) {
|
||||
client.badCertificateCallback = (_, _, _) => true;
|
||||
}
|
||||
return client;
|
||||
},
|
||||
validateCertificate: _ignoreCert ? (_, _, _) => true : null,
|
||||
);
|
||||
|
||||
final data = ValueNotifier<PveRes?>(null);
|
||||
|
||||
bool get onlyOneNode => data.value?.nodes.length == 1;
|
||||
String? release;
|
||||
bool isBusy = false;
|
||||
bool get onlyOneNode => state.data?.nodes.length == 1;
|
||||
|
||||
Future<void> _init() async {
|
||||
try {
|
||||
await _forward();
|
||||
await _login();
|
||||
await _getRelease();
|
||||
state = state.copyWith(isConnected: true);
|
||||
} on PveErr {
|
||||
err.value = l10n.pveLoginFailed;
|
||||
state = state.copyWith(error: PveErr(type: PveErrType.loginFailed, message: l10n.pveLoginFailed));
|
||||
} catch (e, s) {
|
||||
Loggers.app.warning('PVE init failed', e, s);
|
||||
err.value = e.toString();
|
||||
} finally {
|
||||
connected.complete();
|
||||
state = state.copyWith(error: PveErr(type: PveErrType.unknown, message: e.toString()));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,72 +165,81 @@ final class PveProvider extends ChangeNotifier {
|
||||
final resp = await session.get('$addr/api2/extjs/version');
|
||||
final version = resp.data['data']['release'] as String?;
|
||||
if (version != null) {
|
||||
release = version;
|
||||
state = state.copyWith(release: version);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> list() async {
|
||||
await connected.future;
|
||||
if (isBusy) return;
|
||||
isBusy = true;
|
||||
if (!state.isConnected || state.isBusy) return;
|
||||
state = state.copyWith(isBusy: true);
|
||||
try {
|
||||
final resp = await session.get('$addr/api2/json/cluster/resources');
|
||||
final res = resp.data['data'] as List;
|
||||
final result = await Computer.shared.start(PveRes.parse, (
|
||||
res,
|
||||
data.value,
|
||||
state.data,
|
||||
));
|
||||
data.value = result;
|
||||
state = state.copyWith(data: result, error: null);
|
||||
} catch (e) {
|
||||
Loggers.app.warning('PVE list failed', e);
|
||||
err.value = e.toString();
|
||||
state = state.copyWith(error: PveErr(type: PveErrType.unknown, message: e.toString()));
|
||||
} finally {
|
||||
isBusy = false;
|
||||
state = state.copyWith(isBusy: false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> reboot(String node, String id) async {
|
||||
await connected.future;
|
||||
if (!state.isConnected) return false;
|
||||
final resp = await session.post(
|
||||
'$addr/api2/json/nodes/$node/$id/status/reboot',
|
||||
);
|
||||
return _isCtrlSuc(resp);
|
||||
final success = _isCtrlSuc(resp);
|
||||
if (success) await list(); // Refresh data
|
||||
return success;
|
||||
}
|
||||
|
||||
Future<bool> start(String node, String id) async {
|
||||
await connected.future;
|
||||
if (!state.isConnected) return false;
|
||||
final resp = await session.post(
|
||||
'$addr/api2/json/nodes/$node/$id/status/start',
|
||||
);
|
||||
return _isCtrlSuc(resp);
|
||||
final success = _isCtrlSuc(resp);
|
||||
if (success) await list(); // Refresh data
|
||||
return success;
|
||||
}
|
||||
|
||||
Future<bool> stop(String node, String id) async {
|
||||
await connected.future;
|
||||
if (!state.isConnected) return false;
|
||||
final resp = await session.post(
|
||||
'$addr/api2/json/nodes/$node/$id/status/stop',
|
||||
);
|
||||
return _isCtrlSuc(resp);
|
||||
final success = _isCtrlSuc(resp);
|
||||
if (success) await list(); // Refresh data
|
||||
return success;
|
||||
}
|
||||
|
||||
Future<bool> shutdown(String node, String id) async {
|
||||
await connected.future;
|
||||
if (!state.isConnected) return false;
|
||||
final resp = await session.post(
|
||||
'$addr/api2/json/nodes/$node/$id/status/shutdown',
|
||||
);
|
||||
return _isCtrlSuc(resp);
|
||||
final success = _isCtrlSuc(resp);
|
||||
if (success) await list(); // Refresh data
|
||||
return success;
|
||||
}
|
||||
|
||||
bool _isCtrlSuc(Response resp) {
|
||||
return resp.statusCode == 200;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
super.dispose();
|
||||
await _serverSocket.close();
|
||||
try {
|
||||
await _serverSocket.close();
|
||||
} catch (_) {}
|
||||
for (final forward in _forwards) {
|
||||
forward.close();
|
||||
try {
|
||||
forward.close();
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
283
lib/data/provider/pve.freezed.dart
Normal file
283
lib/data/provider/pve.freezed.dart
Normal file
@@ -0,0 +1,283 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'pve.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$PveState {
|
||||
|
||||
PveErr? get error; PveRes? get data; String? get release; bool get isBusy; bool get isConnected;
|
||||
/// Create a copy of PveState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$PveStateCopyWith<PveState> get copyWith => _$PveStateCopyWithImpl<PveState>(this as PveState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is PveState&&(identical(other.error, error) || other.error == error)&&(identical(other.data, data) || other.data == data)&&(identical(other.release, release) || other.release == release)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy)&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,error,data,release,isBusy,isConnected);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PveState(error: $error, data: $data, release: $release, isBusy: $isBusy, isConnected: $isConnected)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $PveStateCopyWith<$Res> {
|
||||
factory $PveStateCopyWith(PveState value, $Res Function(PveState) _then) = _$PveStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
PveErr? error, PveRes? data, String? release, bool isBusy, bool isConnected
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$PveStateCopyWithImpl<$Res>
|
||||
implements $PveStateCopyWith<$Res> {
|
||||
_$PveStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final PveState _self;
|
||||
final $Res Function(PveState) _then;
|
||||
|
||||
/// Create a copy of PveState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? error = freezed,Object? data = freezed,Object? release = freezed,Object? isBusy = null,Object? isConnected = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as PveErr?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as PveRes?,release: freezed == release ? _self.release : release // ignore: cast_nullable_to_non_nullable
|
||||
as String?,isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [PveState].
|
||||
extension PveStatePatterns on PveState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _PveState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PveState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _PveState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PveState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _PveState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _PveState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( PveErr? error, PveRes? data, String? release, bool isBusy, bool isConnected)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PveState() when $default != null:
|
||||
return $default(_that.error,_that.data,_that.release,_that.isBusy,_that.isConnected);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( PveErr? error, PveRes? data, String? release, bool isBusy, bool isConnected) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PveState():
|
||||
return $default(_that.error,_that.data,_that.release,_that.isBusy,_that.isConnected);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( PveErr? error, PveRes? data, String? release, bool isBusy, bool isConnected)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _PveState() when $default != null:
|
||||
return $default(_that.error,_that.data,_that.release,_that.isBusy,_that.isConnected);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _PveState implements PveState {
|
||||
const _PveState({this.error = null, this.data = null, this.release = null, this.isBusy = false, this.isConnected = false});
|
||||
|
||||
|
||||
@override@JsonKey() final PveErr? error;
|
||||
@override@JsonKey() final PveRes? data;
|
||||
@override@JsonKey() final String? release;
|
||||
@override@JsonKey() final bool isBusy;
|
||||
@override@JsonKey() final bool isConnected;
|
||||
|
||||
/// Create a copy of PveState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$PveStateCopyWith<_PveState> get copyWith => __$PveStateCopyWithImpl<_PveState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _PveState&&(identical(other.error, error) || other.error == error)&&(identical(other.data, data) || other.data == data)&&(identical(other.release, release) || other.release == release)&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy)&&(identical(other.isConnected, isConnected) || other.isConnected == isConnected));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,error,data,release,isBusy,isConnected);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'PveState(error: $error, data: $data, release: $release, isBusy: $isBusy, isConnected: $isConnected)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$PveStateCopyWith<$Res> implements $PveStateCopyWith<$Res> {
|
||||
factory _$PveStateCopyWith(_PveState value, $Res Function(_PveState) _then) = __$PveStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
PveErr? error, PveRes? data, String? release, bool isBusy, bool isConnected
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$PveStateCopyWithImpl<$Res>
|
||||
implements _$PveStateCopyWith<$Res> {
|
||||
__$PveStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _PveState _self;
|
||||
final $Res Function(_PveState) _then;
|
||||
|
||||
/// Create a copy of PveState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? error = freezed,Object? data = freezed,Object? release = freezed,Object? isBusy = null,Object? isConnected = null,}) {
|
||||
return _then(_PveState(
|
||||
error: freezed == error ? _self.error : error // ignore: cast_nullable_to_non_nullable
|
||||
as PveErr?,data: freezed == data ? _self.data : data // ignore: cast_nullable_to_non_nullable
|
||||
as PveRes?,release: freezed == release ? _self.release : release // ignore: cast_nullable_to_non_nullable
|
||||
as String?,isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,isConnected: null == isConnected ? _self.isConnected : isConnected // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
160
lib/data/provider/pve.g.dart
Normal file
160
lib/data/provider/pve.g.dart
Normal file
@@ -0,0 +1,160 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'pve.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$pveNotifierHash() => r'667cfb11cd7118d57b29918d137ef2cda2bad7ad';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$PveNotifier extends BuildlessAutoDisposeNotifier<PveState> {
|
||||
late final Spi spiParam;
|
||||
|
||||
PveState build(Spi spiParam);
|
||||
}
|
||||
|
||||
/// See also [PveNotifier].
|
||||
@ProviderFor(PveNotifier)
|
||||
const pveNotifierProvider = PveNotifierFamily();
|
||||
|
||||
/// See also [PveNotifier].
|
||||
class PveNotifierFamily extends Family<PveState> {
|
||||
/// See also [PveNotifier].
|
||||
const PveNotifierFamily();
|
||||
|
||||
/// See also [PveNotifier].
|
||||
PveNotifierProvider call(Spi spiParam) {
|
||||
return PveNotifierProvider(spiParam);
|
||||
}
|
||||
|
||||
@override
|
||||
PveNotifierProvider getProviderOverride(
|
||||
covariant PveNotifierProvider provider,
|
||||
) {
|
||||
return call(provider.spiParam);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'pveNotifierProvider';
|
||||
}
|
||||
|
||||
/// See also [PveNotifier].
|
||||
class PveNotifierProvider
|
||||
extends AutoDisposeNotifierProviderImpl<PveNotifier, PveState> {
|
||||
/// See also [PveNotifier].
|
||||
PveNotifierProvider(Spi spiParam)
|
||||
: this._internal(
|
||||
() => PveNotifier()..spiParam = spiParam,
|
||||
from: pveNotifierProvider,
|
||||
name: r'pveNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$pveNotifierHash,
|
||||
dependencies: PveNotifierFamily._dependencies,
|
||||
allTransitiveDependencies: PveNotifierFamily._allTransitiveDependencies,
|
||||
spiParam: spiParam,
|
||||
);
|
||||
|
||||
PveNotifierProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.spiParam,
|
||||
}) : super.internal();
|
||||
|
||||
final Spi spiParam;
|
||||
|
||||
@override
|
||||
PveState runNotifierBuild(covariant PveNotifier notifier) {
|
||||
return notifier.build(spiParam);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(PveNotifier Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: PveNotifierProvider._internal(
|
||||
() => create()..spiParam = spiParam,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
spiParam: spiParam,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<PveNotifier, PveState> createElement() {
|
||||
return _PveNotifierProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is PveNotifierProvider && other.spiParam == spiParam;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, spiParam.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin PveNotifierRef on AutoDisposeNotifierProviderRef<PveState> {
|
||||
/// The parameter `spiParam` of this provider.
|
||||
Spi get spiParam;
|
||||
}
|
||||
|
||||
class _PveNotifierProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<PveNotifier, PveState>
|
||||
with PveNotifierRef {
|
||||
_PveNotifierProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Spi get spiParam => (origin as PveNotifierProvider).spiParam;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -1,12 +1,12 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
// import 'dart:io';
|
||||
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter_gbk2utf8/flutter_gbk2utf8.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/core/sync.dart';
|
||||
import 'package:server_box/core/utils/server.dart';
|
||||
@@ -24,322 +24,138 @@ import 'package:server_box/data/res/status.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:server_box/data/ssh/session_manager.dart';
|
||||
|
||||
class ServerProvider extends Provider {
|
||||
const ServerProvider._();
|
||||
static const instance = ServerProvider._();
|
||||
part 'server.freezed.dart';
|
||||
part 'server.g.dart';
|
||||
|
||||
static final Map<String, VNode<Server>> servers = {};
|
||||
static final serverOrder = <String>[].vn;
|
||||
static final _tags = <String>{}.vn;
|
||||
static VNode<Set<String>> get tags => _tags;
|
||||
@freezed
|
||||
abstract class ServersState with _$ServersState {
|
||||
const factory ServersState({
|
||||
@Default({}) Map<String, Spi> servers,
|
||||
@Default([]) List<String> serverOrder,
|
||||
@Default(<String>{}) Set<String> tags,
|
||||
@Default(<String>{}) Set<String> manualDisconnectedIds,
|
||||
Timer? autoRefreshTimer,
|
||||
}) = _ServersState;
|
||||
}
|
||||
|
||||
static Timer? _timer;
|
||||
// Individual server state, including connection and status information
|
||||
@freezed
|
||||
abstract class ServerState with _$ServerState {
|
||||
const factory ServerState({
|
||||
required Spi spi,
|
||||
required ServerStatus status,
|
||||
@Default(ServerConn.disconnected) ServerConn conn,
|
||||
SSHClient? client,
|
||||
Future<void>? updateFuture,
|
||||
}) = _ServerState;
|
||||
}
|
||||
|
||||
static final _manualDisconnectedIds = <String>{};
|
||||
extension IndividualServerStateExtension on ServerState {
|
||||
bool get needGenClient => conn < ServerConn.connecting;
|
||||
|
||||
static final _serverIdsUpdating = <String, Future<void>?>{};
|
||||
bool get canViewDetails => conn == ServerConn.finished;
|
||||
|
||||
String get id => spi.id;
|
||||
}
|
||||
|
||||
// Individual server state management
|
||||
@riverpod
|
||||
class IndividualServerNotifier extends _$IndividualServerNotifier {
|
||||
@override
|
||||
Future<void> load() async {
|
||||
super.load();
|
||||
// #147
|
||||
// Clear all servers because of restarting app will cause duplicate servers
|
||||
final oldServers = Map<String, VNode<Server>>.from(servers);
|
||||
servers.clear();
|
||||
serverOrder.value.clear();
|
||||
ServerState build(String serverId) {
|
||||
final serverNotifier = ref.read(serverNotifierProvider);
|
||||
final spi = serverNotifier.servers[serverId];
|
||||
if (spi == null) {
|
||||
throw StateError('Server $serverId not found');
|
||||
}
|
||||
|
||||
final spis = Stores.server.fetch();
|
||||
for (int idx = 0; idx < spis.length; idx++) {
|
||||
final spi = spis[idx];
|
||||
final originServer = oldServers[spi.id];
|
||||
|
||||
/// #258
|
||||
/// If not [shouldReconnect], then keep the old state.
|
||||
if (originServer != null && !originServer.value.spi.shouldReconnect(spi)) {
|
||||
originServer.value.spi = spi;
|
||||
servers[spi.id] = originServer;
|
||||
} else {
|
||||
final newServer = genServer(spi);
|
||||
servers[spi.id] = newServer.vn;
|
||||
}
|
||||
}
|
||||
final serverOrder_ = Stores.setting.serverOrder.fetch();
|
||||
if (serverOrder_.isNotEmpty) {
|
||||
spis.reorder(order: serverOrder_, finder: (n, id) => n.id == id);
|
||||
serverOrder.value.addAll(spis.map((e) => e.id));
|
||||
} else {
|
||||
serverOrder.value.addAll(servers.keys);
|
||||
}
|
||||
// Must use [equals] to compare [Order] here.
|
||||
if (!serverOrder.value.equals(serverOrder_)) {
|
||||
Stores.setting.serverOrder.put(serverOrder.value);
|
||||
}
|
||||
_updateTags();
|
||||
// Must notify here, or the UI will not be updated.
|
||||
serverOrder.notify();
|
||||
return ServerState(spi: spi, status: InitStatus.status);
|
||||
}
|
||||
|
||||
/// Get a [Server] by [spi] or [id].
|
||||
///
|
||||
/// Priority: [spi] > [id]
|
||||
static VNode<Server>? pick({Spi? spi, String? id}) {
|
||||
if (spi != null) {
|
||||
return servers[spi.id];
|
||||
}
|
||||
if (id != null) {
|
||||
return servers[id];
|
||||
}
|
||||
return null;
|
||||
// Update connection status
|
||||
void updateConnection(ServerConn conn) {
|
||||
state = state.copyWith(conn: conn);
|
||||
}
|
||||
|
||||
static void _updateTags() {
|
||||
final tags = <String>{};
|
||||
for (final s in servers.values) {
|
||||
final spiTags = s.value.spi.tags;
|
||||
if (spiTags == null) continue;
|
||||
for (final t in spiTags) {
|
||||
tags.add(t);
|
||||
}
|
||||
}
|
||||
_tags.value = tags;
|
||||
// Update server status
|
||||
void updateStatus(ServerStatus status) {
|
||||
state = state.copyWith(status: status);
|
||||
}
|
||||
|
||||
static Server genServer(Spi spi) {
|
||||
return Server(spi, InitStatus.status, ServerConn.disconnected);
|
||||
// Update SSH client
|
||||
void updateClient(SSHClient? client) {
|
||||
state = state.copyWith(client: client);
|
||||
}
|
||||
|
||||
/// if [spi] is specificed then only refresh this server
|
||||
/// [onlyFailed] only refresh failed servers
|
||||
static Future<void> refresh({Spi? spi, bool onlyFailed = false}) async {
|
||||
if (spi != null) {
|
||||
_manualDisconnectedIds.remove(spi.id);
|
||||
await _getData(spi);
|
||||
// Update SPI configuration
|
||||
void updateSpi(Spi spi) {
|
||||
state = state.copyWith(spi: spi);
|
||||
}
|
||||
|
||||
// Close connection
|
||||
void closeConnection() {
|
||||
final client = state.client;
|
||||
client?.close();
|
||||
state = state.copyWith(client: null, conn: ServerConn.disconnected);
|
||||
}
|
||||
|
||||
// Refresh server status
|
||||
Future<void> refresh() async {
|
||||
if (state.updateFuture != null) {
|
||||
await state.updateFuture;
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.wait(
|
||||
servers.values.map((val) async {
|
||||
final s = val.value;
|
||||
if (onlyFailed) {
|
||||
if (s.conn != ServerConn.failed) return;
|
||||
TryLimiter.reset(s.spi.id);
|
||||
}
|
||||
final updateFuture = _updateServer();
|
||||
state = state.copyWith(updateFuture: updateFuture);
|
||||
|
||||
if (_manualDisconnectedIds.contains(s.spi.id)) return;
|
||||
|
||||
if (s.conn == ServerConn.disconnected && !s.spi.autoConnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if already updating, and if so, wait for it to complete
|
||||
final existingUpdate = _serverIdsUpdating[s.spi.id];
|
||||
if (existingUpdate != null) {
|
||||
// Already updating, wait for the existing update to complete
|
||||
try {
|
||||
await existingUpdate;
|
||||
} catch (e) {
|
||||
// Ignore errors from the existing update, we'll try our own
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Start a new update operation
|
||||
final updateFuture = _updateServer(s.spi);
|
||||
_serverIdsUpdating[s.spi.id] = updateFuture;
|
||||
|
||||
try {
|
||||
await updateFuture;
|
||||
} finally {
|
||||
_serverIdsUpdating.remove(s.spi.id);
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
static Future<void> _updateServer(Spi spi) async {
|
||||
await _getData(spi);
|
||||
}
|
||||
|
||||
static Future<void> startAutoRefresh() async {
|
||||
var duration = Stores.setting.serverStatusUpdateInterval.fetch();
|
||||
stopAutoRefresh();
|
||||
if (duration == 0) return;
|
||||
if (duration < 0 || duration > 10 || duration == 1) {
|
||||
duration = 3;
|
||||
Loggers.app.warning('Invalid duration: $duration, use default 3');
|
||||
}
|
||||
_timer = Timer.periodic(Duration(seconds: duration), (_) async {
|
||||
await refresh();
|
||||
});
|
||||
}
|
||||
|
||||
static void stopAutoRefresh() {
|
||||
if (_timer != null) {
|
||||
_timer!.cancel();
|
||||
_timer = null;
|
||||
try {
|
||||
await updateFuture;
|
||||
} finally {
|
||||
state = state.copyWith(updateFuture: null);
|
||||
}
|
||||
}
|
||||
|
||||
static bool get isAutoRefreshOn => _timer != null;
|
||||
|
||||
static void setDisconnected() {
|
||||
for (final s in servers.values) {
|
||||
s.value.conn = ServerConn.disconnected;
|
||||
s.notify();
|
||||
|
||||
// Update SSH session status to disconnected
|
||||
final sessionId = 'ssh_${s.value.spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
}
|
||||
//TryLimiter.clear();
|
||||
Future<void> _updateServer() async {
|
||||
await _getData();
|
||||
}
|
||||
|
||||
static void closeServer({String? id}) {
|
||||
if (id == null) {
|
||||
for (final s in servers.values) {
|
||||
_closeOneServer(s.value.spi.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_closeOneServer(id);
|
||||
}
|
||||
|
||||
static void _closeOneServer(String id) {
|
||||
final s = servers[id];
|
||||
if (s == null) {
|
||||
Loggers.app.warning('Server with id $id not found');
|
||||
return;
|
||||
}
|
||||
final item = s.value;
|
||||
item.client?.close();
|
||||
item.client = null;
|
||||
item.conn = ServerConn.disconnected;
|
||||
_manualDisconnectedIds.add(id);
|
||||
s.notify();
|
||||
|
||||
// Remove SSH session when server is manually closed
|
||||
final sessionId = 'ssh_$id';
|
||||
TermSessionManager.remove(sessionId);
|
||||
}
|
||||
|
||||
static void addServer(Spi spi) {
|
||||
servers[spi.id] = genServer(spi).vn;
|
||||
Stores.server.put(spi);
|
||||
serverOrder.value.add(spi.id);
|
||||
serverOrder.notify();
|
||||
Stores.setting.serverOrder.put(serverOrder.value);
|
||||
_updateTags();
|
||||
refresh(spi: spi);
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void delServer(String id) {
|
||||
servers.remove(id);
|
||||
serverOrder.value.remove(id);
|
||||
serverOrder.notify();
|
||||
Stores.setting.serverOrder.put(serverOrder.value);
|
||||
Stores.server.delete(id);
|
||||
_updateTags();
|
||||
|
||||
// Remove SSH session when server is deleted
|
||||
final sessionId = 'ssh_$id';
|
||||
TermSessionManager.remove(sessionId);
|
||||
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void deleteAll() {
|
||||
// Remove all SSH sessions before clearing servers
|
||||
for (final id in servers.keys) {
|
||||
final sessionId = 'ssh_$id';
|
||||
TermSessionManager.remove(sessionId);
|
||||
}
|
||||
|
||||
servers.clear();
|
||||
serverOrder.value.clear();
|
||||
serverOrder.notify();
|
||||
Stores.setting.serverOrder.put(serverOrder.value);
|
||||
Stores.server.clear();
|
||||
_updateTags();
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static Future<void> updateServer(Spi old, Spi newSpi) async {
|
||||
if (old != newSpi) {
|
||||
Stores.server.update(old, newSpi);
|
||||
servers[old.id]?.value.spi = newSpi;
|
||||
|
||||
if (newSpi.id != old.id) {
|
||||
servers[newSpi.id] = servers[old.id]!;
|
||||
servers.remove(old.id);
|
||||
serverOrder.value.update(old.id, newSpi.id);
|
||||
Stores.setting.serverOrder.put(serverOrder.value);
|
||||
serverOrder.notify();
|
||||
|
||||
// Update SSH session ID when server ID changes
|
||||
final oldSessionId = 'ssh_${old.id}';
|
||||
TermSessionManager.remove(oldSessionId);
|
||||
// Session will be re-added when reconnecting if necessary
|
||||
}
|
||||
|
||||
// Only reconnect if neccessary
|
||||
if (newSpi.shouldReconnect(old)) {
|
||||
// Use [newSpi.id] instead of [old.id] because [old.id] may be changed
|
||||
TryLimiter.reset(newSpi.id);
|
||||
refresh(spi: newSpi);
|
||||
}
|
||||
}
|
||||
_updateTags();
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void _setServerState(VNode<Server> s, ServerConn ss) {
|
||||
s.value.conn = ss;
|
||||
s.notify();
|
||||
}
|
||||
|
||||
static Future<void> _getData(Spi spi) async {
|
||||
Future<void> _getData() async {
|
||||
final spi = state.spi;
|
||||
final sid = spi.id;
|
||||
final s = servers[sid];
|
||||
|
||||
if (s == null) return;
|
||||
|
||||
final sv = s.value;
|
||||
if (!TryLimiter.canTry(sid)) {
|
||||
if (sv.conn != ServerConn.failed) {
|
||||
_setServerState(s, ServerConn.failed);
|
||||
if (state.conn != ServerConn.failed) {
|
||||
updateConnection(ServerConn.failed);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
sv.status.err = null;
|
||||
final newStatus = state.status..err = null; // Clear previous error
|
||||
updateStatus(newStatus);
|
||||
|
||||
if (sv.needGenClient || (sv.client?.isClosed ?? true)) {
|
||||
_setServerState(s, ServerConn.connecting);
|
||||
if (state.conn < ServerConn.connecting || (state.client?.isClosed ?? true)) {
|
||||
updateConnection(ServerConn.connecting);
|
||||
|
||||
// Wake on LAN
|
||||
final wol = spi.wolCfg;
|
||||
if (wol != null) {
|
||||
try {
|
||||
await wol.wake();
|
||||
} catch (e) {
|
||||
// TryLimiter.inc(sid);
|
||||
// s.status.err = SSHErr(
|
||||
// type: SSHErrType.connect,
|
||||
// message: 'Wake on lan failed: $e',
|
||||
// );
|
||||
// _setServerState(s, ServerConn.failed);
|
||||
Loggers.app.warning('Wake on lan failed', e);
|
||||
// return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
final time1 = DateTime.now();
|
||||
sv.client = await genClient(
|
||||
final client = await genClient(
|
||||
spi,
|
||||
timeout: Duration(seconds: Stores.setting.timeout.fetch()),
|
||||
onKeyboardInteractive: (_) => KeybordInteractive.defaultHandle(spi),
|
||||
);
|
||||
updateClient(client);
|
||||
|
||||
final time2 = DateTime.now();
|
||||
final spentTime = time2.difference(time1).inMilliseconds;
|
||||
if (spi.jumpId == null) {
|
||||
@@ -348,50 +164,57 @@ class ServerProvider extends Provider {
|
||||
Loggers.app.info('Jump to ${spi.name} in $spentTime ms.');
|
||||
}
|
||||
|
||||
// Add SSH session to TermSessionManager
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.add(
|
||||
id: sessionId,
|
||||
spi: spi,
|
||||
startTimeMs: time1.millisecondsSinceEpoch,
|
||||
disconnect: () => _closeOneServer(spi.id),
|
||||
disconnect: () => ref.read(serverNotifierProvider.notifier)._closeOneServer(spi.id),
|
||||
status: TermSessionStatus.connecting,
|
||||
);
|
||||
TermSessionManager.setActive(sessionId, hasTerminal: false);
|
||||
} catch (e) {
|
||||
TryLimiter.inc(sid);
|
||||
sv.status.err = SSHErr(type: SSHErrType.connect, message: e.toString());
|
||||
_setServerState(s, ServerConn.failed);
|
||||
final newStatus = state.status..err = SSHErr(type: SSHErrType.connect, message: e.toString());
|
||||
updateStatus(newStatus);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
// Remove SSH session on connection failure
|
||||
// Remove SSH session when connection fails
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.remove(sessionId);
|
||||
|
||||
/// In order to keep privacy, print [spi.name] instead of [spi.id]
|
||||
Loggers.app.warning('Connect to ${spi.name} failed', e);
|
||||
return;
|
||||
}
|
||||
|
||||
_setServerState(s, ServerConn.connected);
|
||||
updateConnection(ServerConn.connected);
|
||||
|
||||
// Update SSH session status to connected
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.connected);
|
||||
|
||||
try {
|
||||
// Detect system type using helper
|
||||
final detectedSystemType = await SystemDetector.detect(sv.client!, spi);
|
||||
sv.status.system = detectedSystemType;
|
||||
// Detect system type
|
||||
final detectedSystemType = await SystemDetector.detect(state.client!, spi);
|
||||
final newStatus = state.status..system = detectedSystemType;
|
||||
updateStatus(newStatus);
|
||||
|
||||
final (_, writeScriptResult) = await sv.client!.exec((session) async {
|
||||
final scriptRaw = ShellFuncManager.allScript(
|
||||
spi.custom?.cmds,
|
||||
final (_, writeScriptResult) = await state.client!.exec(
|
||||
(session) async {
|
||||
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,
|
||||
disabledCmdTypes: spi.disabledCmdTypes,
|
||||
).uint8List;
|
||||
session.stdin.add(scriptRaw);
|
||||
session.stdin.close();
|
||||
}, entry: ShellFuncManager.getInstallShellCmd(spi.id, systemType: detectedSystemType));
|
||||
customDir: spi.custom?.scriptDir,
|
||||
),
|
||||
);
|
||||
if (writeScriptResult.isNotEmpty && detectedSystemType != SystemType.windows) {
|
||||
ShellFuncManager.switchScriptDir(spi.id, systemType: detectedSystemType);
|
||||
throw writeScriptResult;
|
||||
@@ -399,152 +222,398 @@ class ServerProvider extends Provider {
|
||||
} on SSHAuthAbortError catch (e) {
|
||||
TryLimiter.inc(sid);
|
||||
final err = SSHErr(type: SSHErrType.auth, message: e.toString());
|
||||
sv.status.err = err;
|
||||
final newStatus = state.status..err = err;
|
||||
updateStatus(newStatus);
|
||||
Loggers.app.warning(err);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
// Update SSH session status to disconnected
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
return;
|
||||
} on SSHAuthFailError catch (e) {
|
||||
TryLimiter.inc(sid);
|
||||
final err = SSHErr(type: SSHErrType.auth, message: e.toString());
|
||||
sv.status.err = err;
|
||||
final newStatus = state.status..err = err;
|
||||
updateStatus(newStatus);
|
||||
Loggers.app.warning(err);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
// Update SSH session status to disconnected
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
return;
|
||||
} catch (e) {
|
||||
// If max try times < 2 and can't write script, this will stop the status getting and etc.
|
||||
// TryLimiter.inc(sid);
|
||||
final err = SSHErr(type: SSHErrType.writeScript, message: e.toString());
|
||||
sv.status.err = err;
|
||||
final newStatus = state.status..err = err;
|
||||
updateStatus(newStatus);
|
||||
Loggers.app.warning(err);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
// Update SSH session status to disconnected
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
}
|
||||
}
|
||||
|
||||
if (sv.conn == ServerConn.connecting) return;
|
||||
if (state.conn == ServerConn.connecting) return;
|
||||
|
||||
/// Keep [finished] state, or the UI will be refreshed to [loading] state
|
||||
/// instead of the '$Temp | $Uptime'.
|
||||
/// eg: '32C | 7 days'
|
||||
if (sv.conn != ServerConn.finished) {
|
||||
_setServerState(s, ServerConn.loading);
|
||||
// Keep finished status to prevent UI from refreshing to loading state
|
||||
if (state.conn != ServerConn.finished) {
|
||||
updateConnection(ServerConn.loading);
|
||||
}
|
||||
|
||||
List<String>? segments;
|
||||
String? raw;
|
||||
|
||||
try {
|
||||
final execResult = await sv.client?.run(ShellFunc.status.exec(spi.id, systemType: sv.status.system));
|
||||
if (execResult != null) {
|
||||
String? rawStr;
|
||||
bool needGbk = false;
|
||||
try {
|
||||
rawStr = utf8.decode(execResult, allowMalformed: true);
|
||||
// If there are characters that cannot be parsed, try to fallback to gbk decoding
|
||||
if (rawStr.contains('<EFBFBD>')) {
|
||||
Loggers.app.warning('UTF8 decoding failed, use GBK decoding');
|
||||
needGbk = true;
|
||||
}
|
||||
} catch (e) {
|
||||
Loggers.app.warning('UTF8 decoding failed, use GBK decoding', e);
|
||||
final execResult = await state.client?.run(
|
||||
ShellFunc.status.exec(spi.id, systemType: state.status.system, customDir: spi.custom?.scriptDir),
|
||||
);
|
||||
if (execResult != null) {
|
||||
String? rawStr;
|
||||
bool needGbk = false;
|
||||
try {
|
||||
rawStr = utf8.decode(execResult, allowMalformed: true);
|
||||
// If there are unparseable characters, try fallback to GBK decoding
|
||||
if (rawStr.contains('<EFBFBD>')) {
|
||||
Loggers.app.warning('UTF8 decoding failed, use GBK decoding');
|
||||
needGbk = true;
|
||||
}if (needGbk) {
|
||||
try {
|
||||
rawStr = gbk.decode(execResult);
|
||||
} catch (e2) {
|
||||
Loggers.app.warning('GBK decoding failed', e2);
|
||||
rawStr = null;
|
||||
}
|
||||
}
|
||||
if (rawStr == null) {
|
||||
Loggers.app.warning('Decoding failed, execResult: $execResult');
|
||||
}
|
||||
raw = rawStr;
|
||||
} else {
|
||||
raw = execResult.toString();
|
||||
} catch (e) {
|
||||
Loggers.app.warning('UTF8 decoding failed, use GBK decoding', e);
|
||||
needGbk = true;
|
||||
}
|
||||
if (needGbk) {
|
||||
try {
|
||||
rawStr = gbk.decode(execResult);
|
||||
} catch (e2) {
|
||||
Loggers.app.warning('GBK decoding failed', e2);
|
||||
rawStr = null;
|
||||
}
|
||||
}
|
||||
if (rawStr == null) {
|
||||
Loggers.app.warning('Decoding failed, execResult: $execResult');
|
||||
}
|
||||
raw = rawStr;
|
||||
} else {
|
||||
raw = execResult.toString();
|
||||
}
|
||||
|
||||
if (raw == null || raw.isEmpty) {
|
||||
TryLimiter.inc(sid);
|
||||
sv.status.err = SSHErr(
|
||||
type: SSHErrType.segements,
|
||||
message: 'decode or split failed, raw:\n$raw',
|
||||
);
|
||||
_setServerState(s, ServerConn.failed);
|
||||
final newStatus = state.status
|
||||
..err = SSHErr(type: SSHErrType.segements, message: 'decode or split failed, raw:\n$raw');
|
||||
updateStatus(newStatus);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
// Update SSH session status to disconnected on segments error
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
//dprint('Get status from ${spi.name}:\n$raw');
|
||||
segments = raw.split(ScriptConstants.separator).map((e) => e.trim()).toList();
|
||||
if (raw.isEmpty || segments.isEmpty) {
|
||||
if (Stores.setting.keepStatusWhenErr.fetch()) {
|
||||
// Keep previous server status when err occurs
|
||||
if (sv.conn != ServerConn.failed && sv.status.more.isNotEmpty) {
|
||||
// Keep previous server status when error occurs
|
||||
if (state.conn != ServerConn.failed && state.status.more.isNotEmpty) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
TryLimiter.inc(sid);
|
||||
sv.status.err = SSHErr(type: SSHErrType.segements, message: 'Seperate segments failed, raw:\n$raw');
|
||||
_setServerState(s, ServerConn.failed);
|
||||
final newStatus = state.status
|
||||
..err = SSHErr(type: SSHErrType.segements, message: 'Seperate segments failed, raw:\n$raw');
|
||||
updateStatus(newStatus);
|
||||
updateConnection(ServerConn.failed);
|
||||
|
||||
// Update SSH session status to disconnected on segments error
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
return;
|
||||
}
|
||||
} catch (e) {
|
||||
TryLimiter.inc(sid);
|
||||
sv.status.err = SSHErr(type: SSHErrType.getStatus, message: e.toString());
|
||||
_setServerState(s, ServerConn.failed);
|
||||
final newStatus = state.status..err = SSHErr(type: SSHErrType.getStatus, message: e.toString());
|
||||
updateStatus(newStatus);
|
||||
updateConnection(ServerConn.failed);
|
||||
Loggers.app.warning('Get status from ${spi.name} failed', e);
|
||||
|
||||
// Update SSH session status to disconnected on status error
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Parse script output into command-specific map
|
||||
// Parse script output into command-specific mappings
|
||||
final parsedOutput = ScriptConstants.parseScriptOutput(raw);
|
||||
|
||||
final req = ServerStatusUpdateReq(
|
||||
ss: sv.status,
|
||||
ss: state.status,
|
||||
parsedOutput: parsedOutput,
|
||||
system: sv.status.system,
|
||||
system: state.status.system,
|
||||
customCmds: spi.custom?.cmds ?? {},
|
||||
);
|
||||
sv.status = await Computer.shared.start(getStatus, req, taskName: 'StatusUpdateReq<${sv.id}>');
|
||||
final newStatus = await Computer.shared.start(getStatus, req, taskName: 'StatusUpdateReq<${spi.id}>');
|
||||
updateStatus(newStatus);
|
||||
} catch (e, trace) {
|
||||
TryLimiter.inc(sid);
|
||||
sv.status.err = SSHErr(type: SSHErrType.getStatus, message: 'Parse failed: $e\n\n$raw');
|
||||
_setServerState(s, ServerConn.failed);
|
||||
final newStatus = state.status
|
||||
..err = SSHErr(type: SSHErrType.getStatus, message: 'Parse failed: $e\n\n$raw');
|
||||
updateStatus(newStatus);
|
||||
updateConnection(ServerConn.failed);
|
||||
Loggers.app.warning('Server status', e, trace);
|
||||
|
||||
// Update SSH session status to disconnected on parse error
|
||||
final sessionId = 'ssh_${spi.id}';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
return;
|
||||
}
|
||||
|
||||
/// Call this every time for setting [Server.isBusy] to false
|
||||
_setServerState(s, ServerConn.finished);
|
||||
// reset try times only after prepared successfully
|
||||
// Set Server.isBusy to false each time this method is called
|
||||
updateConnection(ServerConn.finished);
|
||||
// Reset retry count only after successful preparation
|
||||
TryLimiter.reset(sid);
|
||||
}
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class ServerNotifier extends _$ServerNotifier {
|
||||
@override
|
||||
ServersState build() {
|
||||
// Initialize with empty state, load data asynchronously
|
||||
Future.microtask(() => _load());
|
||||
return const ServersState();
|
||||
}
|
||||
|
||||
Future<void> _load() async {
|
||||
final spis = Stores.server.fetch();
|
||||
final newServers = <String, Spi>{};
|
||||
final newServerOrder = <String>[];
|
||||
|
||||
for (final spi in spis) {
|
||||
newServers[spi.id] = spi;
|
||||
}
|
||||
|
||||
final serverOrder_ = Stores.setting.serverOrder.fetch();
|
||||
if (serverOrder_.isNotEmpty) {
|
||||
spis.reorder(order: serverOrder_, finder: (n, id) => n.id == id);
|
||||
newServerOrder.addAll(spis.map((e) => e.id));
|
||||
} else {
|
||||
newServerOrder.addAll(newServers.keys);
|
||||
}
|
||||
|
||||
// Must use [equals] to compare [Order] here.
|
||||
if (!newServerOrder.equals(serverOrder_)) {
|
||||
Stores.setting.serverOrder.put(newServerOrder);
|
||||
}
|
||||
|
||||
final newTags = _calculateTags(newServers);
|
||||
|
||||
state = state.copyWith(servers: newServers, serverOrder: newServerOrder, tags: newTags);
|
||||
}
|
||||
|
||||
Set<String> _calculateTags(Map<String, Spi> servers) {
|
||||
final tags = <String>{};
|
||||
for (final spi in servers.values) {
|
||||
final spiTags = spi.tags;
|
||||
if (spiTags == null) continue;
|
||||
for (final t in spiTags) {
|
||||
tags.add(t);
|
||||
}
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
/// Get a [Spi] by [spi] or [id].
|
||||
///
|
||||
/// Priority: [spi] > [id]
|
||||
Spi? pick({Spi? spi, String? id}) {
|
||||
if (spi != null) {
|
||||
return state.servers[spi.id];
|
||||
}
|
||||
if (id != null) {
|
||||
return state.servers[id];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// if [spi] is specificed then only refresh this server
|
||||
/// [onlyFailed] only refresh failed servers
|
||||
Future<void> refresh({Spi? spi, bool onlyFailed = false}) async {
|
||||
if (spi != null) {
|
||||
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..remove(spi.id);
|
||||
state = state.copyWith(manualDisconnectedIds: newManualDisconnected);
|
||||
final serverNotifier = ref.read(individualServerNotifierProvider(spi.id).notifier);
|
||||
await serverNotifier.refresh();
|
||||
return;
|
||||
}
|
||||
|
||||
await Future.wait(
|
||||
state.servers.entries.map((entry) async {
|
||||
final serverId = entry.key;
|
||||
final spi = entry.value;
|
||||
|
||||
if (onlyFailed) {
|
||||
final serverState = ref.read(individualServerNotifierProvider(serverId));
|
||||
if (serverState.conn != ServerConn.failed) return;
|
||||
TryLimiter.reset(serverId);
|
||||
}
|
||||
|
||||
if (state.manualDisconnectedIds.contains(serverId)) return;
|
||||
|
||||
final serverState = ref.read(individualServerNotifierProvider(serverId));
|
||||
if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) {
|
||||
return;
|
||||
}
|
||||
|
||||
final serverNotifier = ref.read(individualServerNotifierProvider(serverId).notifier);
|
||||
await serverNotifier.refresh();
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> startAutoRefresh() async {
|
||||
var duration = Stores.setting.serverStatusUpdateInterval.fetch();
|
||||
stopAutoRefresh();
|
||||
if (duration == 0) return;
|
||||
if (duration < 0 || duration > 10 || duration == 1) {
|
||||
duration = 3;
|
||||
Loggers.app.warning('Invalid duration: $duration, use default 3');
|
||||
}
|
||||
final timer = Timer.periodic(Duration(seconds: duration), (_) async {
|
||||
await refresh();
|
||||
});
|
||||
state = state.copyWith(autoRefreshTimer: timer);
|
||||
}
|
||||
|
||||
void stopAutoRefresh() {
|
||||
final timer = state.autoRefreshTimer;
|
||||
if (timer != null) {
|
||||
timer.cancel();
|
||||
state = state.copyWith(autoRefreshTimer: null);
|
||||
}
|
||||
}
|
||||
|
||||
bool get isAutoRefreshOn => state.autoRefreshTimer != null;
|
||||
|
||||
void setDisconnected() {
|
||||
for (final serverId in state.servers.keys) {
|
||||
final serverNotifier = ref.read(individualServerNotifierProvider(serverId).notifier);
|
||||
serverNotifier.updateConnection(ServerConn.disconnected);
|
||||
|
||||
// Update SSH session status to disconnected
|
||||
final sessionId = 'ssh_$serverId';
|
||||
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
|
||||
}
|
||||
//TryLimiter.clear();
|
||||
}
|
||||
|
||||
void closeServer({String? id}) {
|
||||
if (id == null) {
|
||||
for (final serverId in state.servers.keys) {
|
||||
_closeOneServer(serverId);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_closeOneServer(id);
|
||||
}
|
||||
|
||||
void _closeOneServer(String id) {
|
||||
final spi = state.servers[id];
|
||||
if (spi == null) {
|
||||
Loggers.app.warning('Server with id $id not found');
|
||||
return;
|
||||
}
|
||||
|
||||
final serverNotifier = ref.read(individualServerNotifierProvider(id).notifier);
|
||||
serverNotifier.closeConnection();
|
||||
|
||||
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..add(id);
|
||||
state = state.copyWith(manualDisconnectedIds: newManualDisconnected);
|
||||
|
||||
// Remove SSH session when server is manually closed
|
||||
final sessionId = 'ssh_$id';
|
||||
TermSessionManager.remove(sessionId);
|
||||
}
|
||||
|
||||
void addServer(Spi spi) {
|
||||
final newServers = Map<String, Spi>.from(state.servers);
|
||||
newServers[spi.id] = spi;
|
||||
|
||||
final newOrder = List<String>.from(state.serverOrder)..add(spi.id);
|
||||
final newTags = _calculateTags(newServers);
|
||||
|
||||
state = state.copyWith(servers: newServers, serverOrder: newOrder, tags: newTags);
|
||||
|
||||
Stores.server.put(spi);
|
||||
Stores.setting.serverOrder.put(newOrder);
|
||||
refresh(spi: spi);
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
void delServer(String id) {
|
||||
final newServers = Map<String, Spi>.from(state.servers);
|
||||
newServers.remove(id);
|
||||
|
||||
final newOrder = List<String>.from(state.serverOrder)..remove(id);
|
||||
final newTags = _calculateTags(newServers);
|
||||
|
||||
state = state.copyWith(servers: newServers, serverOrder: newOrder, tags: newTags);
|
||||
|
||||
Stores.setting.serverOrder.put(newOrder);
|
||||
Stores.server.delete(id);
|
||||
|
||||
// Remove SSH session when server is deleted
|
||||
final sessionId = 'ssh_$id';
|
||||
TermSessionManager.remove(sessionId);
|
||||
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
void deleteAll() {
|
||||
// Remove all SSH sessions before clearing servers
|
||||
for (final id in state.servers.keys) {
|
||||
final sessionId = 'ssh_$id';
|
||||
TermSessionManager.remove(sessionId);
|
||||
}
|
||||
|
||||
state = const ServersState();
|
||||
|
||||
Stores.setting.serverOrder.put([]);
|
||||
Stores.server.clear();
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
Future<void> updateServer(Spi old, Spi newSpi) async {
|
||||
if (old != newSpi) {
|
||||
Stores.server.update(old, newSpi);
|
||||
|
||||
final newServers = Map<String, Spi>.from(state.servers);
|
||||
final newOrder = List<String>.from(state.serverOrder);
|
||||
|
||||
if (newSpi.id != old.id) {
|
||||
newServers[newSpi.id] = newSpi;
|
||||
newServers.remove(old.id);
|
||||
newOrder.update(old.id, newSpi.id);
|
||||
Stores.setting.serverOrder.put(newOrder);
|
||||
|
||||
// Update SSH session ID when server ID changes
|
||||
final oldSessionId = 'ssh_${old.id}';
|
||||
TermSessionManager.remove(oldSessionId);
|
||||
// Session will be re-added when reconnecting if necessary
|
||||
} else {
|
||||
newServers[old.id] = newSpi;
|
||||
// Update SPI in the corresponding IndividualServerNotifier
|
||||
final serverNotifier = ref.read(individualServerNotifierProvider(old.id).notifier);
|
||||
serverNotifier.updateSpi(newSpi);
|
||||
}
|
||||
|
||||
final newTags = _calculateTags(newServers);
|
||||
state = state.copyWith(servers: newServers, serverOrder: newOrder, tags: newTags);
|
||||
|
||||
// Only reconnect if neccessary
|
||||
if (newSpi.shouldReconnect(old)) {
|
||||
// Use [newSpi.id] instead of [old.id] because [old.id] may be changed
|
||||
TryLimiter.reset(newSpi.id);
|
||||
refresh(spi: newSpi);
|
||||
}
|
||||
}
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
597
lib/data/provider/server.freezed.dart
Normal file
597
lib/data/provider/server.freezed.dart
Normal file
@@ -0,0 +1,597 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'server.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$ServersState {
|
||||
|
||||
Map<String, Spi> get servers;// Only store server configuration information
|
||||
List<String> get serverOrder; Set<String> get tags; Set<String> get manualDisconnectedIds; Timer? get autoRefreshTimer;
|
||||
/// Create a copy of ServersState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ServersStateCopyWith<ServersState> get copyWith => _$ServersStateCopyWithImpl<ServersState>(this as ServersState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ServersState&&const DeepCollectionEquality().equals(other.servers, servers)&&const DeepCollectionEquality().equals(other.serverOrder, serverOrder)&&const DeepCollectionEquality().equals(other.tags, tags)&&const DeepCollectionEquality().equals(other.manualDisconnectedIds, manualDisconnectedIds)&&(identical(other.autoRefreshTimer, autoRefreshTimer) || other.autoRefreshTimer == autoRefreshTimer));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(servers),const DeepCollectionEquality().hash(serverOrder),const DeepCollectionEquality().hash(tags),const DeepCollectionEquality().hash(manualDisconnectedIds),autoRefreshTimer);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServersState(servers: $servers, serverOrder: $serverOrder, tags: $tags, manualDisconnectedIds: $manualDisconnectedIds, autoRefreshTimer: $autoRefreshTimer)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ServersStateCopyWith<$Res> {
|
||||
factory $ServersStateCopyWith(ServersState value, $Res Function(ServersState) _then) = _$ServersStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
Map<String, Spi> servers, List<String> serverOrder, Set<String> tags, Set<String> manualDisconnectedIds, Timer? autoRefreshTimer
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ServersStateCopyWithImpl<$Res>
|
||||
implements $ServersStateCopyWith<$Res> {
|
||||
_$ServersStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ServersState _self;
|
||||
final $Res Function(ServersState) _then;
|
||||
|
||||
/// Create a copy of ServersState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? servers = null,Object? serverOrder = null,Object? tags = null,Object? manualDisconnectedIds = null,Object? autoRefreshTimer = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
servers: null == servers ? _self.servers : servers // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, Spi>,serverOrder: null == serverOrder ? _self.serverOrder : serverOrder // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,manualDisconnectedIds: null == manualDisconnectedIds ? _self.manualDisconnectedIds : manualDisconnectedIds // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,autoRefreshTimer: freezed == autoRefreshTimer ? _self.autoRefreshTimer : autoRefreshTimer // ignore: cast_nullable_to_non_nullable
|
||||
as Timer?,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ServersState].
|
||||
extension ServersStatePatterns on ServersState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ServersState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ServersState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ServersState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ServersState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ServersState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ServersState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Map<String, Spi> servers, List<String> serverOrder, Set<String> tags, Set<String> manualDisconnectedIds, Timer? autoRefreshTimer)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ServersState() when $default != null:
|
||||
return $default(_that.servers,_that.serverOrder,_that.tags,_that.manualDisconnectedIds,_that.autoRefreshTimer);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Map<String, Spi> servers, List<String> serverOrder, Set<String> tags, Set<String> manualDisconnectedIds, Timer? autoRefreshTimer) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ServersState():
|
||||
return $default(_that.servers,_that.serverOrder,_that.tags,_that.manualDisconnectedIds,_that.autoRefreshTimer);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Map<String, Spi> servers, List<String> serverOrder, Set<String> tags, Set<String> manualDisconnectedIds, Timer? autoRefreshTimer)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ServersState() when $default != null:
|
||||
return $default(_that.servers,_that.serverOrder,_that.tags,_that.manualDisconnectedIds,_that.autoRefreshTimer);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ServersState implements ServersState {
|
||||
const _ServersState({final Map<String, Spi> servers = const {}, final List<String> serverOrder = const [], final Set<String> tags = const <String>{}, final Set<String> manualDisconnectedIds = const <String>{}, this.autoRefreshTimer}): _servers = servers,_serverOrder = serverOrder,_tags = tags,_manualDisconnectedIds = manualDisconnectedIds;
|
||||
|
||||
|
||||
final Map<String, Spi> _servers;
|
||||
@override@JsonKey() Map<String, Spi> get servers {
|
||||
if (_servers is EqualUnmodifiableMapView) return _servers;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableMapView(_servers);
|
||||
}
|
||||
|
||||
// Only store server configuration information
|
||||
final List<String> _serverOrder;
|
||||
// Only store server configuration information
|
||||
@override@JsonKey() List<String> get serverOrder {
|
||||
if (_serverOrder is EqualUnmodifiableListView) return _serverOrder;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_serverOrder);
|
||||
}
|
||||
|
||||
final Set<String> _tags;
|
||||
@override@JsonKey() Set<String> get tags {
|
||||
if (_tags is EqualUnmodifiableSetView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableSetView(_tags);
|
||||
}
|
||||
|
||||
final Set<String> _manualDisconnectedIds;
|
||||
@override@JsonKey() Set<String> get manualDisconnectedIds {
|
||||
if (_manualDisconnectedIds is EqualUnmodifiableSetView) return _manualDisconnectedIds;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableSetView(_manualDisconnectedIds);
|
||||
}
|
||||
|
||||
@override final Timer? autoRefreshTimer;
|
||||
|
||||
/// Create a copy of ServersState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ServersStateCopyWith<_ServersState> get copyWith => __$ServersStateCopyWithImpl<_ServersState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServersState&&const DeepCollectionEquality().equals(other._servers, _servers)&&const DeepCollectionEquality().equals(other._serverOrder, _serverOrder)&&const DeepCollectionEquality().equals(other._tags, _tags)&&const DeepCollectionEquality().equals(other._manualDisconnectedIds, _manualDisconnectedIds)&&(identical(other.autoRefreshTimer, autoRefreshTimer) || other.autoRefreshTimer == autoRefreshTimer));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_servers),const DeepCollectionEquality().hash(_serverOrder),const DeepCollectionEquality().hash(_tags),const DeepCollectionEquality().hash(_manualDisconnectedIds),autoRefreshTimer);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServersState(servers: $servers, serverOrder: $serverOrder, tags: $tags, manualDisconnectedIds: $manualDisconnectedIds, autoRefreshTimer: $autoRefreshTimer)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ServersStateCopyWith<$Res> implements $ServersStateCopyWith<$Res> {
|
||||
factory _$ServersStateCopyWith(_ServersState value, $Res Function(_ServersState) _then) = __$ServersStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
Map<String, Spi> servers, List<String> serverOrder, Set<String> tags, Set<String> manualDisconnectedIds, Timer? autoRefreshTimer
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ServersStateCopyWithImpl<$Res>
|
||||
implements _$ServersStateCopyWith<$Res> {
|
||||
__$ServersStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ServersState _self;
|
||||
final $Res Function(_ServersState) _then;
|
||||
|
||||
/// Create a copy of ServersState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? servers = null,Object? serverOrder = null,Object? tags = null,Object? manualDisconnectedIds = null,Object? autoRefreshTimer = freezed,}) {
|
||||
return _then(_ServersState(
|
||||
servers: null == servers ? _self._servers : servers // ignore: cast_nullable_to_non_nullable
|
||||
as Map<String, Spi>,serverOrder: null == serverOrder ? _self._serverOrder : serverOrder // ignore: cast_nullable_to_non_nullable
|
||||
as List<String>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,manualDisconnectedIds: null == manualDisconnectedIds ? _self._manualDisconnectedIds : manualDisconnectedIds // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,autoRefreshTimer: freezed == autoRefreshTimer ? _self.autoRefreshTimer : autoRefreshTimer // ignore: cast_nullable_to_non_nullable
|
||||
as Timer?,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$ServerState {
|
||||
|
||||
Spi get spi; ServerStatus get status; ServerConn get conn; SSHClient? get client; Future<void>? get updateFuture;
|
||||
/// Create a copy of ServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$ServerStateCopyWith<ServerState> get copyWith => _$ServerStateCopyWithImpl<ServerState>(this as ServerState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client)&&(identical(other.updateFuture, updateFuture) || other.updateFuture == updateFuture));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,spi,status,conn,client,updateFuture);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client, updateFuture: $updateFuture)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $ServerStateCopyWith<$Res> {
|
||||
factory $ServerStateCopyWith(ServerState value, $Res Function(ServerState) _then) = _$ServerStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture
|
||||
});
|
||||
|
||||
|
||||
$SpiCopyWith<$Res> get spi;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$ServerStateCopyWithImpl<$Res>
|
||||
implements $ServerStateCopyWith<$Res> {
|
||||
_$ServerStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final ServerState _self;
|
||||
final $Res Function(ServerState) _then;
|
||||
|
||||
/// Create a copy of ServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,Object? updateFuture = freezed,}) {
|
||||
return _then(_self.copyWith(
|
||||
spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable
|
||||
as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable
|
||||
as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable
|
||||
as SSHClient?,updateFuture: freezed == updateFuture ? _self.updateFuture : updateFuture // ignore: cast_nullable_to_non_nullable
|
||||
as Future<void>?,
|
||||
));
|
||||
}
|
||||
/// Create a copy of ServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SpiCopyWith<$Res> get spi {
|
||||
|
||||
return $SpiCopyWith<$Res>(_self.spi, (value) {
|
||||
return _then(_self.copyWith(spi: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [ServerState].
|
||||
extension ServerStatePatterns on ServerState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _ServerState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ServerState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _ServerState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ServerState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _ServerState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _ServerState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ServerState() when $default != null:
|
||||
return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ServerState():
|
||||
return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _ServerState() when $default != null:
|
||||
return $default(_that.spi,_that.status,_that.conn,_that.client,_that.updateFuture);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _ServerState implements ServerState {
|
||||
const _ServerState({required this.spi, required this.status, this.conn = ServerConn.disconnected, this.client, this.updateFuture});
|
||||
|
||||
|
||||
@override final Spi spi;
|
||||
@override final ServerStatus status;
|
||||
@override@JsonKey() final ServerConn conn;
|
||||
@override final SSHClient? client;
|
||||
@override final Future<void>? updateFuture;
|
||||
|
||||
/// Create a copy of ServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$ServerStateCopyWith<_ServerState> get copyWith => __$ServerStateCopyWithImpl<_ServerState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _ServerState&&(identical(other.spi, spi) || other.spi == spi)&&(identical(other.status, status) || other.status == status)&&(identical(other.conn, conn) || other.conn == conn)&&(identical(other.client, client) || other.client == client)&&(identical(other.updateFuture, updateFuture) || other.updateFuture == updateFuture));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,spi,status,conn,client,updateFuture);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'ServerState(spi: $spi, status: $status, conn: $conn, client: $client, updateFuture: $updateFuture)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$ServerStateCopyWith<$Res> implements $ServerStateCopyWith<$Res> {
|
||||
factory _$ServerStateCopyWith(_ServerState value, $Res Function(_ServerState) _then) = __$ServerStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
Spi spi, ServerStatus status, ServerConn conn, SSHClient? client, Future<void>? updateFuture
|
||||
});
|
||||
|
||||
|
||||
@override $SpiCopyWith<$Res> get spi;
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$ServerStateCopyWithImpl<$Res>
|
||||
implements _$ServerStateCopyWith<$Res> {
|
||||
__$ServerStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _ServerState _self;
|
||||
final $Res Function(_ServerState) _then;
|
||||
|
||||
/// Create a copy of ServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? spi = null,Object? status = null,Object? conn = null,Object? client = freezed,Object? updateFuture = freezed,}) {
|
||||
return _then(_ServerState(
|
||||
spi: null == spi ? _self.spi : spi // ignore: cast_nullable_to_non_nullable
|
||||
as Spi,status: null == status ? _self.status : status // ignore: cast_nullable_to_non_nullable
|
||||
as ServerStatus,conn: null == conn ? _self.conn : conn // ignore: cast_nullable_to_non_nullable
|
||||
as ServerConn,client: freezed == client ? _self.client : client // ignore: cast_nullable_to_non_nullable
|
||||
as SSHClient?,updateFuture: freezed == updateFuture ? _self.updateFuture : updateFuture // ignore: cast_nullable_to_non_nullable
|
||||
as Future<void>?,
|
||||
));
|
||||
}
|
||||
|
||||
/// Create a copy of ServerState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$SpiCopyWith<$Res> get spi {
|
||||
|
||||
return $SpiCopyWith<$Res>(_self.spi, (value) {
|
||||
return _then(_self.copyWith(spi: value));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// dart format on
|
||||
187
lib/data/provider/server.g.dart
Normal file
187
lib/data/provider/server.g.dart
Normal file
@@ -0,0 +1,187 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'server.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$individualServerNotifierHash() =>
|
||||
r'e3d74fb95ca994cd8419b1deab743e8b3e21bee2';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$IndividualServerNotifier
|
||||
extends BuildlessAutoDisposeNotifier<ServerState> {
|
||||
late final String serverId;
|
||||
|
||||
ServerState build(String serverId);
|
||||
}
|
||||
|
||||
/// See also [IndividualServerNotifier].
|
||||
@ProviderFor(IndividualServerNotifier)
|
||||
const individualServerNotifierProvider = IndividualServerNotifierFamily();
|
||||
|
||||
/// See also [IndividualServerNotifier].
|
||||
class IndividualServerNotifierFamily extends Family<ServerState> {
|
||||
/// See also [IndividualServerNotifier].
|
||||
const IndividualServerNotifierFamily();
|
||||
|
||||
/// See also [IndividualServerNotifier].
|
||||
IndividualServerNotifierProvider call(String serverId) {
|
||||
return IndividualServerNotifierProvider(serverId);
|
||||
}
|
||||
|
||||
@override
|
||||
IndividualServerNotifierProvider getProviderOverride(
|
||||
covariant IndividualServerNotifierProvider provider,
|
||||
) {
|
||||
return call(provider.serverId);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'individualServerNotifierProvider';
|
||||
}
|
||||
|
||||
/// See also [IndividualServerNotifier].
|
||||
class IndividualServerNotifierProvider
|
||||
extends
|
||||
AutoDisposeNotifierProviderImpl<IndividualServerNotifier, ServerState> {
|
||||
/// See also [IndividualServerNotifier].
|
||||
IndividualServerNotifierProvider(String serverId)
|
||||
: this._internal(
|
||||
() => IndividualServerNotifier()..serverId = serverId,
|
||||
from: individualServerNotifierProvider,
|
||||
name: r'individualServerNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$individualServerNotifierHash,
|
||||
dependencies: IndividualServerNotifierFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
IndividualServerNotifierFamily._allTransitiveDependencies,
|
||||
serverId: serverId,
|
||||
);
|
||||
|
||||
IndividualServerNotifierProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.serverId,
|
||||
}) : super.internal();
|
||||
|
||||
final String serverId;
|
||||
|
||||
@override
|
||||
ServerState runNotifierBuild(covariant IndividualServerNotifier notifier) {
|
||||
return notifier.build(serverId);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(IndividualServerNotifier Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: IndividualServerNotifierProvider._internal(
|
||||
() => create()..serverId = serverId,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
serverId: serverId,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<IndividualServerNotifier, ServerState>
|
||||
createElement() {
|
||||
return _IndividualServerNotifierProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is IndividualServerNotifierProvider &&
|
||||
other.serverId == serverId;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, serverId.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin IndividualServerNotifierRef
|
||||
on AutoDisposeNotifierProviderRef<ServerState> {
|
||||
/// The parameter `serverId` of this provider.
|
||||
String get serverId;
|
||||
}
|
||||
|
||||
class _IndividualServerNotifierProviderElement
|
||||
extends
|
||||
AutoDisposeNotifierProviderElement<
|
||||
IndividualServerNotifier,
|
||||
ServerState
|
||||
>
|
||||
with IndividualServerNotifierRef {
|
||||
_IndividualServerNotifierProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
String get serverId => (origin as IndividualServerNotifierProvider).serverId;
|
||||
}
|
||||
|
||||
String _$serverNotifierHash() => r'8e2bc3aef3c56263f88df3c2bb1ba88b6cf83c8f';
|
||||
|
||||
/// See also [ServerNotifier].
|
||||
@ProviderFor(ServerNotifier)
|
||||
final serverNotifierProvider =
|
||||
NotifierProvider<ServerNotifier, ServersState>.internal(
|
||||
ServerNotifier.new,
|
||||
name: r'serverNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$serverNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$ServerNotifier = Notifier<ServersState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -1,41 +1,69 @@
|
||||
import 'dart:async';
|
||||
|
||||
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/data/model/sftp/worker.dart';
|
||||
|
||||
class SftpProvider extends Provider {
|
||||
const SftpProvider._();
|
||||
static const instance = SftpProvider._();
|
||||
part 'sftp.freezed.dart';
|
||||
part 'sftp.g.dart';
|
||||
|
||||
static final status = <SftpReqStatus>[].vn;
|
||||
@freezed
|
||||
abstract class SftpState with _$SftpState {
|
||||
const factory SftpState({
|
||||
@Default(<SftpReqStatus>[]) List<SftpReqStatus> requests,
|
||||
}) = _SftpState;
|
||||
}
|
||||
|
||||
static SftpReqStatus? get(int id) {
|
||||
return status.value.singleWhere((element) => element.id == id);
|
||||
@Riverpod(keepAlive: true)
|
||||
class SftpNotifier extends _$SftpNotifier {
|
||||
@override
|
||||
SftpState build() {
|
||||
return const SftpState();
|
||||
}
|
||||
|
||||
static int add(SftpReq req, {Completer? completer}) {
|
||||
final reqStat = SftpReqStatus(notifyListeners: status.notify, completer: completer, req: req);
|
||||
status.value.add(reqStat);
|
||||
status.notify();
|
||||
SftpReqStatus? get(int id) {
|
||||
try {
|
||||
return state.requests.singleWhere((element) => element.id == id);
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
int add(SftpReq req, {Completer? completer}) {
|
||||
final reqStat = SftpReqStatus(
|
||||
notifyListeners: _notifyListeners,
|
||||
completer: completer,
|
||||
req: req,
|
||||
);
|
||||
state = state.copyWith(
|
||||
requests: [...state.requests, reqStat],
|
||||
);
|
||||
return reqStat.id;
|
||||
}
|
||||
|
||||
static void dispose() {
|
||||
for (final item in status.value) {
|
||||
void dispose() {
|
||||
for (final item in state.requests) {
|
||||
item.dispose();
|
||||
}
|
||||
status.value.clear();
|
||||
status.notify();
|
||||
state = state.copyWith(requests: []);
|
||||
}
|
||||
|
||||
static void cancel(int id) {
|
||||
final idx = status.value.indexWhere((e) => e.id == id);
|
||||
if (idx < 0 || idx >= status.value.length) {
|
||||
void cancel(int id) {
|
||||
final idx = state.requests.indexWhere((e) => e.id == id);
|
||||
if (idx < 0 || idx >= state.requests.length) {
|
||||
dprint('SftpProvider.cancel: id $id not found');
|
||||
return;
|
||||
}
|
||||
status.value[idx].dispose();
|
||||
status.value.removeAt(idx);
|
||||
status.notify();
|
||||
final item = state.requests[idx];
|
||||
item.dispose();
|
||||
final newRequests = List<SftpReqStatus>.from(state.requests)
|
||||
..removeAt(idx);
|
||||
state = state.copyWith(requests: newRequests);
|
||||
}
|
||||
|
||||
void _notifyListeners() {
|
||||
// Force state update to notify listeners
|
||||
state = state.copyWith();
|
||||
}
|
||||
}
|
||||
|
||||
277
lib/data/provider/sftp.freezed.dart
Normal file
277
lib/data/provider/sftp.freezed.dart
Normal file
@@ -0,0 +1,277 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'sftp.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$SftpState {
|
||||
|
||||
List<SftpReqStatus> get requests;
|
||||
/// Create a copy of SftpState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SftpStateCopyWith<SftpState> get copyWith => _$SftpStateCopyWithImpl<SftpState>(this as SftpState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SftpState&&const DeepCollectionEquality().equals(other.requests, requests));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(requests));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SftpState(requests: $requests)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SftpStateCopyWith<$Res> {
|
||||
factory $SftpStateCopyWith(SftpState value, $Res Function(SftpState) _then) = _$SftpStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<SftpReqStatus> requests
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SftpStateCopyWithImpl<$Res>
|
||||
implements $SftpStateCopyWith<$Res> {
|
||||
_$SftpStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SftpState _self;
|
||||
final $Res Function(SftpState) _then;
|
||||
|
||||
/// Create a copy of SftpState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? requests = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
requests: null == requests ? _self.requests : requests // ignore: cast_nullable_to_non_nullable
|
||||
as List<SftpReqStatus>,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SftpState].
|
||||
extension SftpStatePatterns on SftpState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SftpState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SftpState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SftpState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SftpState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SftpState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SftpState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<SftpReqStatus> requests)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SftpState() when $default != null:
|
||||
return $default(_that.requests);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<SftpReqStatus> requests) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SftpState():
|
||||
return $default(_that.requests);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<SftpReqStatus> requests)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SftpState() when $default != null:
|
||||
return $default(_that.requests);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _SftpState implements SftpState {
|
||||
const _SftpState({final List<SftpReqStatus> requests = const <SftpReqStatus>[]}): _requests = requests;
|
||||
|
||||
|
||||
final List<SftpReqStatus> _requests;
|
||||
@override@JsonKey() List<SftpReqStatus> get requests {
|
||||
if (_requests is EqualUnmodifiableListView) return _requests;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_requests);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of SftpState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SftpStateCopyWith<_SftpState> get copyWith => __$SftpStateCopyWithImpl<_SftpState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SftpState&&const DeepCollectionEquality().equals(other._requests, _requests));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_requests));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SftpState(requests: $requests)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SftpStateCopyWith<$Res> implements $SftpStateCopyWith<$Res> {
|
||||
factory _$SftpStateCopyWith(_SftpState value, $Res Function(_SftpState) _then) = __$SftpStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<SftpReqStatus> requests
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SftpStateCopyWithImpl<$Res>
|
||||
implements _$SftpStateCopyWith<$Res> {
|
||||
__$SftpStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SftpState _self;
|
||||
final $Res Function(_SftpState) _then;
|
||||
|
||||
/// Create a copy of SftpState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? requests = null,}) {
|
||||
return _then(_SftpState(
|
||||
requests: null == requests ? _self._requests : requests // ignore: cast_nullable_to_non_nullable
|
||||
as List<SftpReqStatus>,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
25
lib/data/provider/sftp.g.dart
Normal file
25
lib/data/provider/sftp.g.dart
Normal file
@@ -0,0 +1,25 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'sftp.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$sftpNotifierHash() => r'f8412a4bd1f2bc5919ec31a3eba1c27e9a578f41';
|
||||
|
||||
/// See also [SftpNotifier].
|
||||
@ProviderFor(SftpNotifier)
|
||||
final sftpNotifierProvider = NotifierProvider<SftpNotifier, SftpState>.internal(
|
||||
SftpNotifier.new,
|
||||
name: r'sftpNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$sftpNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SftpNotifier = Notifier<SftpState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -1,22 +1,31 @@
|
||||
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/sync.dart';
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class SnippetProvider extends Provider {
|
||||
const SnippetProvider._();
|
||||
static const instance = SnippetProvider._();
|
||||
part 'snippet.freezed.dart';
|
||||
part 'snippet.g.dart';
|
||||
|
||||
static final snippets = <Snippet>[].vn;
|
||||
static final tags = <String>{}.vn;
|
||||
@freezed
|
||||
abstract class SnippetState with _$SnippetState {
|
||||
const factory SnippetState({
|
||||
@Default(<Snippet>[]) List<Snippet> snippets,
|
||||
@Default(<String>{}) Set<String> tags,
|
||||
}) = _SnippetState;
|
||||
}
|
||||
|
||||
@Riverpod(keepAlive: true)
|
||||
class SnippetNotifier extends _$SnippetNotifier {
|
||||
@override
|
||||
void load() {
|
||||
super.load();
|
||||
final snippets_ = Stores.snippet.fetch();
|
||||
SnippetState build() {
|
||||
final snippets = Stores.snippet.fetch();
|
||||
final order = Stores.setting.snippetOrder.fetch();
|
||||
|
||||
List<Snippet> orderedSnippets = snippets;
|
||||
if (order.isNotEmpty) {
|
||||
final surplus = snippets_.reorder(
|
||||
final surplus = snippets.reorder(
|
||||
order: order,
|
||||
finder: (n, name) => n.name == name,
|
||||
);
|
||||
@@ -24,57 +33,65 @@ class SnippetProvider extends Provider {
|
||||
if (order != Stores.setting.snippetOrder.fetch()) {
|
||||
Stores.setting.snippetOrder.put(order);
|
||||
}
|
||||
orderedSnippets = snippets;
|
||||
}
|
||||
snippets.value = snippets_;
|
||||
_updateTags();
|
||||
|
||||
final tags = _computeTags(orderedSnippets);
|
||||
return SnippetState(snippets: orderedSnippets, tags: tags);
|
||||
}
|
||||
|
||||
static void _updateTags() {
|
||||
final tags_ = <String>{};
|
||||
for (final s in snippets.value) {
|
||||
Set<String> _computeTags(List<Snippet> snippets) {
|
||||
final tags = <String>{};
|
||||
for (final s in snippets) {
|
||||
final t = s.tags;
|
||||
if (t != null) {
|
||||
tags_.addAll(t);
|
||||
tags.addAll(t);
|
||||
}
|
||||
}
|
||||
tags.value = tags_;
|
||||
return tags;
|
||||
}
|
||||
|
||||
static void add(Snippet snippet) {
|
||||
snippets.value.add(snippet);
|
||||
snippets.notify();
|
||||
void add(Snippet snippet) {
|
||||
final newSnippets = [...state.snippets, snippet];
|
||||
final newTags = _computeTags(newSnippets);
|
||||
state = state.copyWith(snippets: newSnippets, tags: newTags);
|
||||
Stores.snippet.put(snippet);
|
||||
_updateTags();
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void del(Snippet snippet) {
|
||||
snippets.value.remove(snippet);
|
||||
snippets.notify();
|
||||
void del(Snippet snippet) {
|
||||
final newSnippets = state.snippets.where((s) => s != snippet).toList();
|
||||
final newTags = _computeTags(newSnippets);
|
||||
state = state.copyWith(snippets: newSnippets, tags: newTags);
|
||||
Stores.snippet.delete(snippet);
|
||||
_updateTags();
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void update(Snippet old, Snippet newOne) {
|
||||
snippets.value.remove(old);
|
||||
snippets.value.add(newOne);
|
||||
snippets.notify();
|
||||
void update(Snippet old, Snippet newOne) {
|
||||
final newSnippets = state.snippets.map((s) => s == old ? newOne : s).toList();
|
||||
final newTags = _computeTags(newSnippets);
|
||||
state = state.copyWith(snippets: newSnippets, tags: newTags);
|
||||
Stores.snippet.delete(old);
|
||||
Stores.snippet.put(newOne);
|
||||
_updateTags();
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
static void renameTag(String old, String newOne) {
|
||||
for (final s in snippets.value) {
|
||||
void renameTag(String old, String newOne) {
|
||||
final updatedSnippets = <Snippet>[];
|
||||
for (final s in state.snippets) {
|
||||
if (s.tags?.contains(old) ?? false) {
|
||||
s.tags?.remove(old);
|
||||
s.tags?.add(newOne);
|
||||
Stores.snippet.put(s);
|
||||
final newTags = Set<String>.from(s.tags!);
|
||||
newTags.remove(old);
|
||||
newTags.add(newOne);
|
||||
final updatedSnippet = s.copyWith(tags: newTags.toList());
|
||||
updatedSnippets.add(updatedSnippet);
|
||||
Stores.snippet.put(updatedSnippet);
|
||||
} else {
|
||||
updatedSnippets.add(s);
|
||||
}
|
||||
}
|
||||
_updateTags();
|
||||
final newTags = _computeTags(updatedSnippets);
|
||||
state = state.copyWith(snippets: updatedSnippets, tags: newTags);
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
}
|
||||
|
||||
286
lib/data/provider/snippet.freezed.dart
Normal file
286
lib/data/provider/snippet.freezed.dart
Normal file
@@ -0,0 +1,286 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'snippet.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$SnippetState {
|
||||
|
||||
List<Snippet> get snippets; Set<String> get tags;
|
||||
/// Create a copy of SnippetState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SnippetStateCopyWith<SnippetState> get copyWith => _$SnippetStateCopyWithImpl<SnippetState>(this as SnippetState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SnippetState&&const DeepCollectionEquality().equals(other.snippets, snippets)&&const DeepCollectionEquality().equals(other.tags, tags));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(snippets),const DeepCollectionEquality().hash(tags));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnippetState(snippets: $snippets, tags: $tags)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SnippetStateCopyWith<$Res> {
|
||||
factory $SnippetStateCopyWith(SnippetState value, $Res Function(SnippetState) _then) = _$SnippetStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
List<Snippet> snippets, Set<String> tags
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SnippetStateCopyWithImpl<$Res>
|
||||
implements $SnippetStateCopyWith<$Res> {
|
||||
_$SnippetStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SnippetState _self;
|
||||
final $Res Function(SnippetState) _then;
|
||||
|
||||
/// Create a copy of SnippetState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? snippets = null,Object? tags = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
snippets: null == snippets ? _self.snippets : snippets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Snippet>,tags: null == tags ? _self.tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SnippetState].
|
||||
extension SnippetStatePatterns on SnippetState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SnippetState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnippetState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SnippetState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnippetState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SnippetState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SnippetState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( List<Snippet> snippets, Set<String> tags)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnippetState() when $default != null:
|
||||
return $default(_that.snippets,_that.tags);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( List<Snippet> snippets, Set<String> tags) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnippetState():
|
||||
return $default(_that.snippets,_that.tags);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( List<Snippet> snippets, Set<String> tags)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SnippetState() when $default != null:
|
||||
return $default(_that.snippets,_that.tags);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _SnippetState implements SnippetState {
|
||||
const _SnippetState({final List<Snippet> snippets = const <Snippet>[], final Set<String> tags = const <String>{}}): _snippets = snippets,_tags = tags;
|
||||
|
||||
|
||||
final List<Snippet> _snippets;
|
||||
@override@JsonKey() List<Snippet> get snippets {
|
||||
if (_snippets is EqualUnmodifiableListView) return _snippets;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_snippets);
|
||||
}
|
||||
|
||||
final Set<String> _tags;
|
||||
@override@JsonKey() Set<String> get tags {
|
||||
if (_tags is EqualUnmodifiableSetView) return _tags;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableSetView(_tags);
|
||||
}
|
||||
|
||||
|
||||
/// Create a copy of SnippetState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SnippetStateCopyWith<_SnippetState> get copyWith => __$SnippetStateCopyWithImpl<_SnippetState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SnippetState&&const DeepCollectionEquality().equals(other._snippets, _snippets)&&const DeepCollectionEquality().equals(other._tags, _tags));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,const DeepCollectionEquality().hash(_snippets),const DeepCollectionEquality().hash(_tags));
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SnippetState(snippets: $snippets, tags: $tags)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SnippetStateCopyWith<$Res> implements $SnippetStateCopyWith<$Res> {
|
||||
factory _$SnippetStateCopyWith(_SnippetState value, $Res Function(_SnippetState) _then) = __$SnippetStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
List<Snippet> snippets, Set<String> tags
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SnippetStateCopyWithImpl<$Res>
|
||||
implements _$SnippetStateCopyWith<$Res> {
|
||||
__$SnippetStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SnippetState _self;
|
||||
final $Res Function(_SnippetState) _then;
|
||||
|
||||
/// Create a copy of SnippetState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? snippets = null,Object? tags = null,}) {
|
||||
return _then(_SnippetState(
|
||||
snippets: null == snippets ? _self._snippets : snippets // ignore: cast_nullable_to_non_nullable
|
||||
as List<Snippet>,tags: null == tags ? _self._tags : tags // ignore: cast_nullable_to_non_nullable
|
||||
as Set<String>,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
26
lib/data/provider/snippet.g.dart
Normal file
26
lib/data/provider/snippet.g.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'snippet.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$snippetNotifierHash() => r'caf0361f9a0346fb99cb90f032f1ceb29446dd71';
|
||||
|
||||
/// See also [SnippetNotifier].
|
||||
@ProviderFor(SnippetNotifier)
|
||||
final snippetNotifierProvider =
|
||||
NotifierProvider<SnippetNotifier, SnippetState>.internal(
|
||||
SnippetNotifier.new,
|
||||
name: r'snippetNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$snippetNotifierHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$SnippetNotifier = Notifier<SnippetState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -1,45 +1,57 @@
|
||||
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.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;
|
||||
part 'systemd.freezed.dart';
|
||||
part 'systemd.g.dart';
|
||||
|
||||
SystemdProvider.init(Spi spi) {
|
||||
_si = ServerProvider.pick(spi: spi)!;
|
||||
getUnits();
|
||||
}
|
||||
@freezed
|
||||
abstract class SystemdState with _$SystemdState {
|
||||
const factory SystemdState({
|
||||
@Default(false) bool isBusy,
|
||||
@Default(<SystemdUnit>[]) List<SystemdUnit> units,
|
||||
@Default(SystemdScopeFilter.all) SystemdScopeFilter scopeFilter,
|
||||
}) = _SystemdState;
|
||||
}
|
||||
|
||||
final isBusy = false.vn;
|
||||
final units = <SystemdUnit>[].vn;
|
||||
final scopeFilter = SystemdScopeFilter.all.vn;
|
||||
@riverpod
|
||||
class SystemdNotifier extends _$SystemdNotifier {
|
||||
late final ServerState _si;
|
||||
|
||||
void dispose() {
|
||||
isBusy.dispose();
|
||||
units.dispose();
|
||||
scopeFilter.dispose();
|
||||
@override
|
||||
SystemdState build(Spi spi) {
|
||||
final si = ref.read(individualServerNotifierProvider(spi.id));
|
||||
_si = si;
|
||||
// Async initialization
|
||||
Future.microtask(() => getUnits());
|
||||
return const SystemdState();
|
||||
}
|
||||
|
||||
List<SystemdUnit> get filteredUnits {
|
||||
switch (scopeFilter.value) {
|
||||
switch (state.scopeFilter) {
|
||||
case SystemdScopeFilter.all:
|
||||
return units.value;
|
||||
return state.units;
|
||||
case SystemdScopeFilter.system:
|
||||
return units.value.where((unit) => unit.scope == SystemdUnitScope.system).toList();
|
||||
return state.units.where((unit) => unit.scope == SystemdUnitScope.system).toList();
|
||||
case SystemdScopeFilter.user:
|
||||
return units.value.where((unit) => unit.scope == SystemdUnitScope.user).toList();
|
||||
return state.units.where((unit) => unit.scope == SystemdUnitScope.user).toList();
|
||||
}
|
||||
}
|
||||
|
||||
void setScopeFilter(SystemdScopeFilter filter) {
|
||||
state = state.copyWith(scopeFilter: filter);
|
||||
}
|
||||
|
||||
Future<void> getUnits() async {
|
||||
isBusy.value = true;
|
||||
state = state.copyWith(isBusy: true);
|
||||
|
||||
try {
|
||||
final client = _si.value.client;
|
||||
final client = _si.client;
|
||||
final result = await client!.execForOutput(_getUnitsCmd);
|
||||
final units = result.split('\n');
|
||||
|
||||
@@ -57,12 +69,11 @@ final class SystemdProvider {
|
||||
|
||||
final parsedUserUnits = await _parseUnitObj(userUnits, SystemdUnitScope.user);
|
||||
final parsedSystemUnits = await _parseUnitObj(systemUnits, SystemdUnitScope.system);
|
||||
this.units.value = [...parsedUserUnits, ...parsedSystemUnits];
|
||||
state = state.copyWith(units: [...parsedUserUnits, ...parsedSystemUnits], isBusy: false);
|
||||
} catch (e, s) {
|
||||
dprint('Parse systemd', e, s);
|
||||
state = state.copyWith(isBusy: false);
|
||||
}
|
||||
|
||||
isBusy.value = false;
|
||||
}
|
||||
|
||||
Future<List<SystemdUnit>> _parseUnitObj(List<String> unitNames, SystemdUnitScope scope) async {
|
||||
@@ -75,7 +86,7 @@ for unit in ${unitNames_.join(' ')}; do
|
||||
echo -n "\n${ScriptConstants.separator}\n"
|
||||
done
|
||||
''';
|
||||
final client = _si.value.client!;
|
||||
final client = _si.client!;
|
||||
final result = await client.execForOutput(script);
|
||||
final units = result.split(ScriptConstants.separator);
|
||||
|
||||
|
||||
283
lib/data/provider/systemd.freezed.dart
Normal file
283
lib/data/provider/systemd.freezed.dart
Normal file
@@ -0,0 +1,283 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'systemd.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$SystemdState {
|
||||
|
||||
bool get isBusy; List<SystemdUnit> get units; SystemdScopeFilter get scopeFilter;
|
||||
/// Create a copy of SystemdState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$SystemdStateCopyWith<SystemdState> get copyWith => _$SystemdStateCopyWithImpl<SystemdState>(this as SystemdState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is SystemdState&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy)&&const DeepCollectionEquality().equals(other.units, units)&&(identical(other.scopeFilter, scopeFilter) || other.scopeFilter == scopeFilter));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isBusy,const DeepCollectionEquality().hash(units),scopeFilter);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SystemdState(isBusy: $isBusy, units: $units, scopeFilter: $scopeFilter)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $SystemdStateCopyWith<$Res> {
|
||||
factory $SystemdStateCopyWith(SystemdState value, $Res Function(SystemdState) _then) = _$SystemdStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool isBusy, List<SystemdUnit> units, SystemdScopeFilter scopeFilter
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$SystemdStateCopyWithImpl<$Res>
|
||||
implements $SystemdStateCopyWith<$Res> {
|
||||
_$SystemdStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final SystemdState _self;
|
||||
final $Res Function(SystemdState) _then;
|
||||
|
||||
/// Create a copy of SystemdState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? isBusy = null,Object? units = null,Object? scopeFilter = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,units: null == units ? _self.units : units // ignore: cast_nullable_to_non_nullable
|
||||
as List<SystemdUnit>,scopeFilter: null == scopeFilter ? _self.scopeFilter : scopeFilter // ignore: cast_nullable_to_non_nullable
|
||||
as SystemdScopeFilter,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [SystemdState].
|
||||
extension SystemdStatePatterns on SystemdState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _SystemdState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SystemdState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _SystemdState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SystemdState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _SystemdState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _SystemdState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool isBusy, List<SystemdUnit> units, SystemdScopeFilter scopeFilter)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SystemdState() when $default != null:
|
||||
return $default(_that.isBusy,_that.units,_that.scopeFilter);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool isBusy, List<SystemdUnit> units, SystemdScopeFilter scopeFilter) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SystemdState():
|
||||
return $default(_that.isBusy,_that.units,_that.scopeFilter);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool isBusy, List<SystemdUnit> units, SystemdScopeFilter scopeFilter)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _SystemdState() when $default != null:
|
||||
return $default(_that.isBusy,_that.units,_that.scopeFilter);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _SystemdState implements SystemdState {
|
||||
const _SystemdState({this.isBusy = false, final List<SystemdUnit> units = const <SystemdUnit>[], this.scopeFilter = SystemdScopeFilter.all}): _units = units;
|
||||
|
||||
|
||||
@override@JsonKey() final bool isBusy;
|
||||
final List<SystemdUnit> _units;
|
||||
@override@JsonKey() List<SystemdUnit> get units {
|
||||
if (_units is EqualUnmodifiableListView) return _units;
|
||||
// ignore: implicit_dynamic_type
|
||||
return EqualUnmodifiableListView(_units);
|
||||
}
|
||||
|
||||
@override@JsonKey() final SystemdScopeFilter scopeFilter;
|
||||
|
||||
/// Create a copy of SystemdState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$SystemdStateCopyWith<_SystemdState> get copyWith => __$SystemdStateCopyWithImpl<_SystemdState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _SystemdState&&(identical(other.isBusy, isBusy) || other.isBusy == isBusy)&&const DeepCollectionEquality().equals(other._units, _units)&&(identical(other.scopeFilter, scopeFilter) || other.scopeFilter == scopeFilter));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,isBusy,const DeepCollectionEquality().hash(_units),scopeFilter);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'SystemdState(isBusy: $isBusy, units: $units, scopeFilter: $scopeFilter)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$SystemdStateCopyWith<$Res> implements $SystemdStateCopyWith<$Res> {
|
||||
factory _$SystemdStateCopyWith(_SystemdState value, $Res Function(_SystemdState) _then) = __$SystemdStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool isBusy, List<SystemdUnit> units, SystemdScopeFilter scopeFilter
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$SystemdStateCopyWithImpl<$Res>
|
||||
implements _$SystemdStateCopyWith<$Res> {
|
||||
__$SystemdStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _SystemdState _self;
|
||||
final $Res Function(_SystemdState) _then;
|
||||
|
||||
/// Create a copy of SystemdState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? isBusy = null,Object? units = null,Object? scopeFilter = null,}) {
|
||||
return _then(_SystemdState(
|
||||
isBusy: null == isBusy ? _self.isBusy : isBusy // ignore: cast_nullable_to_non_nullable
|
||||
as bool,units: null == units ? _self._units : units // ignore: cast_nullable_to_non_nullable
|
||||
as List<SystemdUnit>,scopeFilter: null == scopeFilter ? _self.scopeFilter : scopeFilter // ignore: cast_nullable_to_non_nullable
|
||||
as SystemdScopeFilter,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
163
lib/data/provider/systemd.g.dart
Normal file
163
lib/data/provider/systemd.g.dart
Normal file
@@ -0,0 +1,163 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'systemd.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$systemdNotifierHash() => r'617fb7637fbc5c5100e5b522d246984f22b44cca';
|
||||
|
||||
/// Copied from Dart SDK
|
||||
class _SystemHash {
|
||||
_SystemHash._();
|
||||
|
||||
static int combine(int hash, int value) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + value);
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10));
|
||||
return hash ^ (hash >> 6);
|
||||
}
|
||||
|
||||
static int finish(int hash) {
|
||||
// ignore: parameter_assignments
|
||||
hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3));
|
||||
// ignore: parameter_assignments
|
||||
hash = hash ^ (hash >> 11);
|
||||
return 0x1fffffff & (hash + ((0x00003fff & hash) << 15));
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _$SystemdNotifier
|
||||
extends BuildlessAutoDisposeNotifier<SystemdState> {
|
||||
late final Spi spi;
|
||||
|
||||
SystemdState build(Spi spi);
|
||||
}
|
||||
|
||||
/// See also [SystemdNotifier].
|
||||
@ProviderFor(SystemdNotifier)
|
||||
const systemdNotifierProvider = SystemdNotifierFamily();
|
||||
|
||||
/// See also [SystemdNotifier].
|
||||
class SystemdNotifierFamily extends Family<SystemdState> {
|
||||
/// See also [SystemdNotifier].
|
||||
const SystemdNotifierFamily();
|
||||
|
||||
/// See also [SystemdNotifier].
|
||||
SystemdNotifierProvider call(Spi spi) {
|
||||
return SystemdNotifierProvider(spi);
|
||||
}
|
||||
|
||||
@override
|
||||
SystemdNotifierProvider getProviderOverride(
|
||||
covariant SystemdNotifierProvider provider,
|
||||
) {
|
||||
return call(provider.spi);
|
||||
}
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||
|
||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||
|
||||
@override
|
||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||
_allTransitiveDependencies;
|
||||
|
||||
@override
|
||||
String? get name => r'systemdNotifierProvider';
|
||||
}
|
||||
|
||||
/// See also [SystemdNotifier].
|
||||
class SystemdNotifierProvider
|
||||
extends AutoDisposeNotifierProviderImpl<SystemdNotifier, SystemdState> {
|
||||
/// See also [SystemdNotifier].
|
||||
SystemdNotifierProvider(Spi spi)
|
||||
: this._internal(
|
||||
() => SystemdNotifier()..spi = spi,
|
||||
from: systemdNotifierProvider,
|
||||
name: r'systemdNotifierProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$systemdNotifierHash,
|
||||
dependencies: SystemdNotifierFamily._dependencies,
|
||||
allTransitiveDependencies:
|
||||
SystemdNotifierFamily._allTransitiveDependencies,
|
||||
spi: spi,
|
||||
);
|
||||
|
||||
SystemdNotifierProvider._internal(
|
||||
super._createNotifier, {
|
||||
required super.name,
|
||||
required super.dependencies,
|
||||
required super.allTransitiveDependencies,
|
||||
required super.debugGetCreateSourceHash,
|
||||
required super.from,
|
||||
required this.spi,
|
||||
}) : super.internal();
|
||||
|
||||
final Spi spi;
|
||||
|
||||
@override
|
||||
SystemdState runNotifierBuild(covariant SystemdNotifier notifier) {
|
||||
return notifier.build(spi);
|
||||
}
|
||||
|
||||
@override
|
||||
Override overrideWith(SystemdNotifier Function() create) {
|
||||
return ProviderOverride(
|
||||
origin: this,
|
||||
override: SystemdNotifierProvider._internal(
|
||||
() => create()..spi = spi,
|
||||
from: from,
|
||||
name: null,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
debugGetCreateSourceHash: null,
|
||||
spi: spi,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
AutoDisposeNotifierProviderElement<SystemdNotifier, SystemdState>
|
||||
createElement() {
|
||||
return _SystemdNotifierProviderElement(this);
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return other is SystemdNotifierProvider && other.spi == spi;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode {
|
||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||
hash = _SystemHash.combine(hash, spi.hashCode);
|
||||
|
||||
return _SystemHash.finish(hash);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||
// ignore: unused_element
|
||||
mixin SystemdNotifierRef on AutoDisposeNotifierProviderRef<SystemdState> {
|
||||
/// The parameter `spi` of this provider.
|
||||
Spi get spi;
|
||||
}
|
||||
|
||||
class _SystemdNotifierProviderElement
|
||||
extends AutoDisposeNotifierProviderElement<SystemdNotifier, SystemdState>
|
||||
with SystemdNotifierRef {
|
||||
_SystemdNotifierProviderElement(super.provider);
|
||||
|
||||
@override
|
||||
Spi get spi => (origin as SystemdNotifierProvider).spi;
|
||||
}
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -1,56 +1,63 @@
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:xterm/core.dart';
|
||||
|
||||
class VirtKeyProvider extends TerminalInputHandler with ChangeNotifier {
|
||||
VirtKeyProvider();
|
||||
part 'virtual_keyboard.g.dart';
|
||||
part 'virtual_keyboard.freezed.dart';
|
||||
|
||||
bool _ctrl = false;
|
||||
bool get ctrl => _ctrl;
|
||||
set ctrl(bool value) {
|
||||
if (value != _ctrl) {
|
||||
_ctrl = value;
|
||||
notifyListeners();
|
||||
@freezed
|
||||
abstract class VirtKeyState with _$VirtKeyState {
|
||||
const factory VirtKeyState({
|
||||
@Default(false) final bool ctrl,
|
||||
@Default(false) final bool alt,
|
||||
@Default(false) final bool shift,
|
||||
}) = _VirtKeyState;
|
||||
}
|
||||
|
||||
@riverpod
|
||||
class VirtKeyboard extends _$VirtKeyboard implements TerminalInputHandler {
|
||||
@override
|
||||
VirtKeyState build() {
|
||||
return const VirtKeyState();
|
||||
}
|
||||
|
||||
bool get ctrl => state.ctrl;
|
||||
bool get alt => state.alt;
|
||||
bool get shift => state.shift;
|
||||
|
||||
void setCtrl(bool value) {
|
||||
if (value != state.ctrl) {
|
||||
state = state.copyWith(ctrl: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _alt = false;
|
||||
bool get alt => _alt;
|
||||
set alt(bool value) {
|
||||
if (value != _alt) {
|
||||
_alt = value;
|
||||
notifyListeners();
|
||||
void setAlt(bool value) {
|
||||
if (value != state.alt) {
|
||||
state = state.copyWith(alt: value);
|
||||
}
|
||||
}
|
||||
|
||||
bool _shift = false;
|
||||
bool get shift => _shift;
|
||||
set shift(bool value) {
|
||||
if (value != _shift) {
|
||||
_shift = value;
|
||||
notifyListeners();
|
||||
void setShift(bool value) {
|
||||
if (value != state.shift) {
|
||||
state = state.copyWith(shift: value);
|
||||
}
|
||||
}
|
||||
|
||||
void reset(TerminalKeyboardEvent e) {
|
||||
if (e.ctrl) {
|
||||
ctrl = false;
|
||||
}
|
||||
if (e.alt) {
|
||||
alt = false;
|
||||
}
|
||||
if (e.shift) {
|
||||
shift = false;
|
||||
}
|
||||
notifyListeners();
|
||||
state = state.copyWith(
|
||||
ctrl: e.ctrl ? false : state.ctrl,
|
||||
alt: e.alt ? false : state.alt,
|
||||
shift: e.shift ? false : state.shift,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String? call(TerminalKeyboardEvent event) {
|
||||
final e = event.copyWith(
|
||||
ctrl: event.ctrl || ctrl,
|
||||
alt: event.alt || alt,
|
||||
shift: event.shift || shift,
|
||||
ctrl: event.ctrl || state.ctrl,
|
||||
alt: event.alt || state.alt,
|
||||
shift: event.shift || state.shift,
|
||||
);
|
||||
if (Stores.setting.sshVirtualKeyAutoOff.fetch()) {
|
||||
reset(e);
|
||||
|
||||
277
lib/data/provider/virtual_keyboard.freezed.dart
Normal file
277
lib/data/provider/virtual_keyboard.freezed.dart
Normal file
@@ -0,0 +1,277 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// coverage:ignore-file
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'virtual_keyboard.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
// dart format off
|
||||
T _$identity<T>(T value) => value;
|
||||
/// @nodoc
|
||||
mixin _$VirtKeyState {
|
||||
|
||||
bool get ctrl; bool get alt; bool get shift;
|
||||
/// Create a copy of VirtKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
$VirtKeyStateCopyWith<VirtKeyState> get copyWith => _$VirtKeyStateCopyWithImpl<VirtKeyState>(this as VirtKeyState, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is VirtKeyState&&(identical(other.ctrl, ctrl) || other.ctrl == ctrl)&&(identical(other.alt, alt) || other.alt == alt)&&(identical(other.shift, shift) || other.shift == shift));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,ctrl,alt,shift);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VirtKeyState(ctrl: $ctrl, alt: $alt, shift: $shift)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class $VirtKeyStateCopyWith<$Res> {
|
||||
factory $VirtKeyStateCopyWith(VirtKeyState value, $Res Function(VirtKeyState) _then) = _$VirtKeyStateCopyWithImpl;
|
||||
@useResult
|
||||
$Res call({
|
||||
bool ctrl, bool alt, bool shift
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class _$VirtKeyStateCopyWithImpl<$Res>
|
||||
implements $VirtKeyStateCopyWith<$Res> {
|
||||
_$VirtKeyStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final VirtKeyState _self;
|
||||
final $Res Function(VirtKeyState) _then;
|
||||
|
||||
/// Create a copy of VirtKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline') @override $Res call({Object? ctrl = null,Object? alt = null,Object? shift = null,}) {
|
||||
return _then(_self.copyWith(
|
||||
ctrl: null == ctrl ? _self.ctrl : ctrl // ignore: cast_nullable_to_non_nullable
|
||||
as bool,alt: null == alt ? _self.alt : alt // ignore: cast_nullable_to_non_nullable
|
||||
as bool,shift: null == shift ? _self.shift : shift // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// Adds pattern-matching-related methods to [VirtKeyState].
|
||||
extension VirtKeyStatePatterns on VirtKeyState {
|
||||
/// A variant of `map` that fallback to returning `orElse`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeMap<TResult extends Object?>(TResult Function( _VirtKeyState value)? $default,{required TResult orElse(),}){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _VirtKeyState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// Callbacks receives the raw object, upcasted.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case final Subclass2 value:
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult map<TResult extends Object?>(TResult Function( _VirtKeyState value) $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _VirtKeyState():
|
||||
return $default(_that);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `map` that fallback to returning `null`.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case final Subclass value:
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? mapOrNull<TResult extends Object?>(TResult? Function( _VirtKeyState value)? $default,){
|
||||
final _that = this;
|
||||
switch (_that) {
|
||||
case _VirtKeyState() when $default != null:
|
||||
return $default(_that);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to an `orElse` callback.
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return orElse();
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult maybeWhen<TResult extends Object?>(TResult Function( bool ctrl, bool alt, bool shift)? $default,{required TResult orElse(),}) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VirtKeyState() when $default != null:
|
||||
return $default(_that.ctrl,_that.alt,_that.shift);case _:
|
||||
return orElse();
|
||||
|
||||
}
|
||||
}
|
||||
/// A `switch`-like method, using callbacks.
|
||||
///
|
||||
/// As opposed to `map`, this offers destructuring.
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case Subclass2(:final field2):
|
||||
/// return ...;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult when<TResult extends Object?>(TResult Function( bool ctrl, bool alt, bool shift) $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VirtKeyState():
|
||||
return $default(_that.ctrl,_that.alt,_that.shift);case _:
|
||||
throw StateError('Unexpected subclass');
|
||||
|
||||
}
|
||||
}
|
||||
/// A variant of `when` that fallback to returning `null`
|
||||
///
|
||||
/// It is equivalent to doing:
|
||||
/// ```dart
|
||||
/// switch (sealedClass) {
|
||||
/// case Subclass(:final field):
|
||||
/// return ...;
|
||||
/// case _:
|
||||
/// return null;
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
@optionalTypeArgs TResult? whenOrNull<TResult extends Object?>(TResult? Function( bool ctrl, bool alt, bool shift)? $default,) {final _that = this;
|
||||
switch (_that) {
|
||||
case _VirtKeyState() when $default != null:
|
||||
return $default(_that.ctrl,_that.alt,_that.shift);case _:
|
||||
return null;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
|
||||
|
||||
class _VirtKeyState implements VirtKeyState {
|
||||
const _VirtKeyState({this.ctrl = false, this.alt = false, this.shift = false});
|
||||
|
||||
|
||||
@override@JsonKey() final bool ctrl;
|
||||
@override@JsonKey() final bool alt;
|
||||
@override@JsonKey() final bool shift;
|
||||
|
||||
/// Create a copy of VirtKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@pragma('vm:prefer-inline')
|
||||
_$VirtKeyStateCopyWith<_VirtKeyState> get copyWith => __$VirtKeyStateCopyWithImpl<_VirtKeyState>(this, _$identity);
|
||||
|
||||
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) || (other.runtimeType == runtimeType&&other is _VirtKeyState&&(identical(other.ctrl, ctrl) || other.ctrl == ctrl)&&(identical(other.alt, alt) || other.alt == alt)&&(identical(other.shift, shift) || other.shift == shift));
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
int get hashCode => Object.hash(runtimeType,ctrl,alt,shift);
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'VirtKeyState(ctrl: $ctrl, alt: $alt, shift: $shift)';
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract mixin class _$VirtKeyStateCopyWith<$Res> implements $VirtKeyStateCopyWith<$Res> {
|
||||
factory _$VirtKeyStateCopyWith(_VirtKeyState value, $Res Function(_VirtKeyState) _then) = __$VirtKeyStateCopyWithImpl;
|
||||
@override @useResult
|
||||
$Res call({
|
||||
bool ctrl, bool alt, bool shift
|
||||
});
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
/// @nodoc
|
||||
class __$VirtKeyStateCopyWithImpl<$Res>
|
||||
implements _$VirtKeyStateCopyWith<$Res> {
|
||||
__$VirtKeyStateCopyWithImpl(this._self, this._then);
|
||||
|
||||
final _VirtKeyState _self;
|
||||
final $Res Function(_VirtKeyState) _then;
|
||||
|
||||
/// Create a copy of VirtKeyState
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override @pragma('vm:prefer-inline') $Res call({Object? ctrl = null,Object? alt = null,Object? shift = null,}) {
|
||||
return _then(_VirtKeyState(
|
||||
ctrl: null == ctrl ? _self.ctrl : ctrl // ignore: cast_nullable_to_non_nullable
|
||||
as bool,alt: null == alt ? _self.alt : alt // ignore: cast_nullable_to_non_nullable
|
||||
as bool,shift: null == shift ? _self.shift : shift // ignore: cast_nullable_to_non_nullable
|
||||
as bool,
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// dart format on
|
||||
26
lib/data/provider/virtual_keyboard.g.dart
Normal file
26
lib/data/provider/virtual_keyboard.g.dart
Normal file
@@ -0,0 +1,26 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'virtual_keyboard.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// RiverpodGenerator
|
||||
// **************************************************************************
|
||||
|
||||
String _$virtKeyboardHash() => r'1327d412bfb0dd261f3b555f353a8852b4f753e5';
|
||||
|
||||
/// See also [VirtKeyboard].
|
||||
@ProviderFor(VirtKeyboard)
|
||||
final virtKeyboardProvider =
|
||||
AutoDisposeNotifierProvider<VirtKeyboard, VirtKeyState>.internal(
|
||||
VirtKeyboard.new,
|
||||
name: r'virtKeyboardProvider',
|
||||
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
|
||||
? null
|
||||
: _$virtKeyboardHash,
|
||||
dependencies: null,
|
||||
allTransitiveDependencies: null,
|
||||
);
|
||||
|
||||
typedef _$VirtKeyboard = AutoDisposeNotifier<VirtKeyState>;
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||
@@ -5,18 +5,13 @@ import 'dart:async';
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_displaymode/flutter_displaymode.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:hive_ce_flutter/hive_flutter.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
import 'package:server_box/app.dart';
|
||||
import 'package:server_box/core/sync.dart';
|
||||
import 'package:server_box/data/model/app/menu/server_func.dart';
|
||||
import 'package:server_box/data/model/app/server_detail_card.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';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:server_box/data/ssh/session_manager.dart';
|
||||
@@ -26,7 +21,7 @@ import 'package:server_box/hive/hive_registrar.g.dart';
|
||||
Future<void> main() async {
|
||||
_runInZone(() async {
|
||||
await _initApp();
|
||||
runApp(const MyApp());
|
||||
runApp(ProviderScope(child: const MyApp()));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -66,12 +61,6 @@ Future<void> _initData() async {
|
||||
// DO DB migration before load any provider.
|
||||
await _doDbMigrate();
|
||||
|
||||
// DO NOT change the order of these providers.
|
||||
PrivateKeyProvider.instance.load();
|
||||
SnippetProvider.instance.load();
|
||||
ServerProvider.instance.load();
|
||||
SftpProvider.instance.load();
|
||||
|
||||
if (Stores.setting.betaTest.fetch()) AppUpdate.chan = AppUpdateChan.beta;
|
||||
|
||||
FontUtils.loadFrom(Stores.setting.fontPath.fetch());
|
||||
@@ -94,10 +83,7 @@ void _doPlatformRelated() async {
|
||||
}
|
||||
|
||||
final serversCount = Stores.server.keys().length;
|
||||
BackgroundIsolateBinaryMessenger.ensureInitialized(RootIsolateToken.instance!);
|
||||
Computer.shared.turnOn(workersCount: (serversCount / 3).round() + 1); // Plus 1 to avoid 0.
|
||||
|
||||
bakSync.sync();
|
||||
}
|
||||
|
||||
// It may contains some async heavy funcs.
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:computer/computer.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/sync.dart';
|
||||
@@ -17,16 +18,16 @@ import 'package:server_box/data/res/misc.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:webdav_client_plus/webdav_client_plus.dart';
|
||||
|
||||
class BackupPage extends StatefulWidget {
|
||||
class BackupPage extends ConsumerStatefulWidget {
|
||||
const BackupPage({super.key});
|
||||
|
||||
@override
|
||||
State<BackupPage> createState() => _BackupPageState();
|
||||
ConsumerState<BackupPage> createState() => _BackupPageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: BackupPage.new, path: '/backup');
|
||||
}
|
||||
|
||||
final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveClientMixin {
|
||||
final class _BackupPageState extends ConsumerState<BackupPage> with AutomaticKeepAliveClientMixin {
|
||||
final webdavLoading = false.vn;
|
||||
final gistLoading = false.vn;
|
||||
|
||||
@@ -401,8 +402,9 @@ final class _BackupPageState extends State<BackupPage> with AutomaticKeepAliveCl
|
||||
child: SingleChildScrollView(child: Text(libL10n.askContinue('${libL10n.import} [$snippetNames]'))),
|
||||
actions: Btn.ok(
|
||||
onTap: () {
|
||||
final notifier = ref.read(snippetNotifierProvider.notifier);
|
||||
for (final snippet in snippets) {
|
||||
SnippetProvider.add(snippet);
|
||||
notifier.add(snippet);
|
||||
}
|
||||
context.pop();
|
||||
context.pop();
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
part of 'container.dart';
|
||||
|
||||
extension on _ContainerPageState {
|
||||
/// The notifier for the container state.
|
||||
ContainerNotifier get _containerNotifier => ref.read(_provider.notifier);
|
||||
|
||||
/// Watch the current state of the container.
|
||||
ContainerState get _containerState => ref.watch(_provider);
|
||||
|
||||
Future<void> _showAddFAB() async {
|
||||
final imageCtrl = TextEditingController();
|
||||
final nameCtrl = TextEditingController();
|
||||
@@ -79,7 +85,7 @@ extension on _ContainerPageState {
|
||||
onPressed: () async {
|
||||
context.pop();
|
||||
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _container.run(cmd));
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.run(cmd));
|
||||
if (err != null || result != null) {
|
||||
final e = result?.message ?? err?.toString();
|
||||
context.showRoundDialog(title: libL10n.error, child: Text(e.toString()));
|
||||
@@ -111,7 +117,7 @@ extension on _ContainerPageState {
|
||||
void _onSaveDockerHost(String val) {
|
||||
context.pop();
|
||||
Stores.container.put(widget.args.spi.id, val.trim());
|
||||
_container.refresh();
|
||||
_containerNotifier.refresh();
|
||||
}
|
||||
|
||||
void _showImageRmDialog(ContainerImg e) {
|
||||
@@ -121,7 +127,7 @@ extension on _ContainerPageState {
|
||||
actions: Btn.ok(
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
final result = await _container.run('rmi ${e.id} -f');
|
||||
final result = await _containerNotifier.run('rmi ${e.id} -f');
|
||||
if (result != null) {
|
||||
context.showSnackBar(result.message ?? 'null');
|
||||
}
|
||||
@@ -163,7 +169,9 @@ extension on _ContainerPageState {
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _container.delete(id, force));
|
||||
final (result, err) = await context.showLoadingDialog(
|
||||
fn: () => _containerNotifier.delete(id, force),
|
||||
);
|
||||
if (err != null || result != null) {
|
||||
final e = result?.message ?? err?.toString();
|
||||
context.showRoundDialog(title: libL10n.error, child: Text(e ?? 'null'));
|
||||
@@ -173,21 +181,21 @@ extension on _ContainerPageState {
|
||||
);
|
||||
break;
|
||||
case ContainerMenu.start:
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _container.start(id));
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.start(id));
|
||||
if (err != null || result != null) {
|
||||
final e = result?.message ?? err?.toString();
|
||||
context.showRoundDialog(title: libL10n.error, child: Text(e ?? 'null'));
|
||||
}
|
||||
break;
|
||||
case ContainerMenu.stop:
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _container.stop(id));
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.stop(id));
|
||||
if (err != null || result != null) {
|
||||
final e = result?.message ?? err?.toString();
|
||||
context.showRoundDialog(title: libL10n.error, child: Text(e ?? 'null'));
|
||||
}
|
||||
break;
|
||||
case ContainerMenu.restart:
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _container.restart(id));
|
||||
final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.restart(id));
|
||||
if (err != null || result != null) {
|
||||
final e = result?.message ?? err?.toString();
|
||||
context.showRoundDialog(title: libL10n.error, child: Text(e ?? 'null'));
|
||||
@@ -197,7 +205,7 @@ extension on _ContainerPageState {
|
||||
final args = SshPageArgs(
|
||||
spi: widget.args.spi,
|
||||
initCmd:
|
||||
'${switch (_container.type) {
|
||||
'${switch (_containerState.type) {
|
||||
ContainerType.podman => 'podman',
|
||||
ContainerType.docker => 'docker',
|
||||
}} logs -f --tail 100 ${dItem.id}',
|
||||
@@ -208,7 +216,7 @@ extension on _ContainerPageState {
|
||||
final args = SshPageArgs(
|
||||
spi: widget.args.spi,
|
||||
initCmd:
|
||||
'${switch (_container.type) {
|
||||
'${switch (_containerState.type) {
|
||||
ContainerType.podman => 'podman',
|
||||
ContainerType.docker => 'docker',
|
||||
}} exec -it ${dItem.id} sh',
|
||||
@@ -222,7 +230,7 @@ extension on _ContainerPageState {
|
||||
if (Stores.setting.containerAutoRefresh.fetch()) {
|
||||
Timer.periodic(Duration(seconds: Stores.setting.serverStatusUpdateInterval.fetch()), (timer) {
|
||||
if (mounted) {
|
||||
_container.refresh(isAuto: true);
|
||||
_containerNotifier.refresh(isAuto: true);
|
||||
} else {
|
||||
timer.cancel();
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'dart:async';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/route.dart';
|
||||
import 'package:server_box/data/model/app/error.dart';
|
||||
@@ -12,81 +12,79 @@ import 'package:server_box/data/model/app/menu/container.dart';
|
||||
import 'package:server_box/data/model/container/image.dart';
|
||||
import 'package:server_box/data/model/container/ps.dart';
|
||||
import 'package:server_box/data/model/container/type.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/provider/container.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:server_box/view/page/ssh/page/page.dart';
|
||||
|
||||
part 'actions.dart';
|
||||
part 'types.dart';
|
||||
|
||||
class ContainerPage extends StatefulWidget {
|
||||
class ContainerPage extends ConsumerStatefulWidget {
|
||||
final SpiRequiredArgs args;
|
||||
const ContainerPage({required this.args, super.key});
|
||||
|
||||
@override
|
||||
State<ContainerPage> createState() => _ContainerPageState();
|
||||
ConsumerState<ContainerPage> createState() => _ContainerPageState();
|
||||
|
||||
static const route = AppRouteArg(page: ContainerPage.new, path: '/container');
|
||||
}
|
||||
|
||||
class _ContainerPageState extends State<ContainerPage> {
|
||||
class _ContainerPageState extends ConsumerState<ContainerPage> {
|
||||
final _textController = TextEditingController();
|
||||
late final _container = ContainerProvider(
|
||||
client: widget.args.spi.server?.value.client,
|
||||
userName: widget.args.spi.user,
|
||||
hostId: widget.args.spi.id,
|
||||
context: context,
|
||||
);
|
||||
late final ContainerNotifierProvider _provider;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_textController.dispose();
|
||||
_container.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final serverState = ref.read(individualServerNotifierProvider(widget.args.spi.id));
|
||||
_provider = containerNotifierProvider(
|
||||
serverState.client,
|
||||
widget.args.spi.user,
|
||||
widget.args.spi.id,
|
||||
context,
|
||||
);
|
||||
_initAutoRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ChangeNotifierProvider(
|
||||
create: (_) => _container,
|
||||
builder: (_, _) => Consumer<ContainerProvider>(
|
||||
builder: (_, _, _) {
|
||||
return Scaffold(
|
||||
appBar: _buildAppBar,
|
||||
body: SafeArea(child: _buildMain),
|
||||
floatingActionButton: _container.error == null ? _buildFAB : null,
|
||||
);
|
||||
},
|
||||
),
|
||||
final err = ref.watch(_provider.select((p) => p.error));
|
||||
|
||||
return Scaffold(
|
||||
appBar: _buildAppBar(),
|
||||
body: SafeArea(child: _buildMain()),
|
||||
floatingActionButton: err == null ? _buildFAB() : null,
|
||||
);
|
||||
}
|
||||
|
||||
CustomAppBar get _buildAppBar {
|
||||
CustomAppBar _buildAppBar() {
|
||||
return CustomAppBar(
|
||||
centerTitle: true,
|
||||
title: TwoLineText(up: l10n.container, down: widget.args.spi.name),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () => context.showLoadingDialog(fn: () => _container.refresh()),
|
||||
onPressed: () => context.showLoadingDialog(fn: () => _containerNotifier.refresh()),
|
||||
icon: const Icon(Icons.refresh),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget get _buildFAB {
|
||||
Widget _buildFAB() {
|
||||
return FloatingActionButton(onPressed: () async => await _showAddFAB(), child: const Icon(Icons.add));
|
||||
}
|
||||
|
||||
Widget get _buildMain {
|
||||
if (_container.error != null && _container.items == null) {
|
||||
Widget _buildMain() {
|
||||
final containerState = _containerState;
|
||||
|
||||
if (containerState.error != null && containerState.items == null) {
|
||||
return SizedBox.expand(
|
||||
child: Column(
|
||||
children: [
|
||||
@@ -95,7 +93,7 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
UIs.height13,
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 23),
|
||||
child: Text(_container.error.toString()),
|
||||
child: Text(containerState.error.toString()),
|
||||
),
|
||||
const Spacer(),
|
||||
UIs.height13,
|
||||
@@ -104,27 +102,27 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
).paddingSymmetric(horizontal: 13),
|
||||
);
|
||||
}
|
||||
if (_container.items == null || _container.images == null) {
|
||||
if (containerState.items == null || containerState.images == null) {
|
||||
return UIs.centerLoading;
|
||||
}
|
||||
|
||||
return AutoMultiList(
|
||||
children: <Widget>[
|
||||
_buildLoading(),
|
||||
_buildVersion(),
|
||||
_buildPs(),
|
||||
_buildImage(),
|
||||
_buildEmptyStateMessage(),
|
||||
_buildLoading(containerState),
|
||||
_buildVersion(containerState),
|
||||
_buildPs(containerState),
|
||||
_buildImage(containerState),
|
||||
_buildEmptyStateMessage(containerState),
|
||||
_buildPruneBtns,
|
||||
_buildSettingsBtns,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEmptyStateMessage() {
|
||||
final emptyImgs = _container.images?.isEmpty ?? true;
|
||||
final emptyPs = _container.items?.isEmpty ?? true;
|
||||
if (emptyPs && emptyImgs && _container.runLog == null) {
|
||||
Widget _buildEmptyStateMessage(ContainerState containerState) {
|
||||
final emptyImgs = containerState.images?.isEmpty ?? true;
|
||||
final emptyPs = containerState.items?.isEmpty ?? true;
|
||||
if (emptyPs && emptyImgs && containerState.runLog == null) {
|
||||
return CardX(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(17, 17, 17, 7),
|
||||
@@ -135,13 +133,13 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
return UIs.placeholder;
|
||||
}
|
||||
|
||||
Widget _buildImage() {
|
||||
Widget _buildImage(ContainerState containerState) {
|
||||
return ExpandTile(
|
||||
leading: const Icon(MingCute.clapperboard_line),
|
||||
title: Text(l10n.imagesList),
|
||||
subtitle: Text(l10n.dockerImagesFmt(_container.images!.length), style: UIs.textGrey),
|
||||
initiallyExpanded: (_container.images?.length ?? 0) <= 3,
|
||||
children: _container.images?.map(_buildImageItem).toList() ?? [],
|
||||
subtitle: Text(l10n.dockerImagesFmt(containerState.images?.length ?? 'null'), style: UIs.textGrey),
|
||||
initiallyExpanded: (containerState.images?.length ?? 0) <= 3,
|
||||
children: containerState.images?.map(_buildImageItem).toList() ?? [],
|
||||
).cardx;
|
||||
}
|
||||
|
||||
@@ -161,34 +159,34 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildLoading() {
|
||||
if (_container.runLog == null) return UIs.placeholder;
|
||||
Widget _buildLoading(ContainerState containerState) {
|
||||
if (containerState.runLog == null) return UIs.placeholder;
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(17),
|
||||
child: Column(
|
||||
children: [
|
||||
const Center(child: CircularProgressIndicator()),
|
||||
UIs.height13,
|
||||
Text(_container.runLog ?? '...'),
|
||||
Text(containerState.runLog ?? '...'),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVersion() {
|
||||
Widget _buildVersion(ContainerState containerState) {
|
||||
return CardX(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(17),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: [Text(_container.type.name.capitalize), Text(_container.version ?? l10n.unknown)],
|
||||
children: [Text(containerState.type.name.capitalize), Text(containerState.version ?? l10n.unknown)],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPs() {
|
||||
final items = _container.items;
|
||||
Widget _buildPs(ContainerState containerState) {
|
||||
final items = containerState.items;
|
||||
if (items == null) return UIs.placeholder;
|
||||
final running = items.where((e) => e.running).length;
|
||||
final stopped = items.length - running;
|
||||
@@ -309,16 +307,17 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
|
||||
Widget _buildPruneBtn(_PruneTypes type) {
|
||||
final title = type.name.capitalize;
|
||||
final containerNotifier = _containerNotifier;
|
||||
return ListTile(
|
||||
onTap: () async {
|
||||
await _showPruneDialog(
|
||||
title: title,
|
||||
message: type.tip,
|
||||
onConfirm: switch (type) {
|
||||
_PruneTypes.images => _container.pruneImages,
|
||||
_PruneTypes.containers => _container.pruneContainers,
|
||||
_PruneTypes.volumes => _container.pruneVolumes,
|
||||
_PruneTypes.system => _container.pruneSystem,
|
||||
_PruneTypes.images => containerNotifier.pruneImages,
|
||||
_PruneTypes.containers => containerNotifier.pruneContainers,
|
||||
_PruneTypes.volumes => containerNotifier.pruneVolumes,
|
||||
_PruneTypes.system => containerNotifier.pruneSystem,
|
||||
},
|
||||
);
|
||||
},
|
||||
@@ -330,22 +329,26 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
Widget get _buildSettingsBtns {
|
||||
final len = _SettingsMenuItems.values.length;
|
||||
if (len == 0) return UIs.placeholder;
|
||||
final containerState = _containerState;
|
||||
|
||||
return ExpandTile(
|
||||
leading: const Icon(Icons.settings),
|
||||
title: Text(libL10n.setting),
|
||||
initiallyExpanded: _container.error != null,
|
||||
children: _SettingsMenuItems.values.map(_buildSettingTile).toList(),
|
||||
initiallyExpanded: containerState.error != null,
|
||||
children: _SettingsMenuItems.values.map((item) => _buildSettingTile(item, containerState)).toList(),
|
||||
).cardx;
|
||||
}
|
||||
|
||||
Widget _buildSettingTile(_SettingsMenuItems item) {
|
||||
Widget _buildSettingTile(_SettingsMenuItems item, ContainerState containerState) {
|
||||
final String title;
|
||||
switch (item) {
|
||||
case _SettingsMenuItems.editDockerHost:
|
||||
title = '${libL10n.edit} DOCKER_HOST';
|
||||
break;
|
||||
case _SettingsMenuItems.switchProvider:
|
||||
title = _container.type == ContainerType.podman ? l10n.switchTo('Docker') : l10n.switchTo('Podman');
|
||||
title = containerState.type == ContainerType.podman
|
||||
? l10n.switchTo('Docker')
|
||||
: l10n.switchTo('Podman');
|
||||
break;
|
||||
}
|
||||
return ListTile(
|
||||
@@ -355,9 +358,11 @@ class _ContainerPageState extends State<ContainerPage> {
|
||||
_showEditHostDialog();
|
||||
break;
|
||||
case _SettingsMenuItems.switchProvider:
|
||||
_container.setType(
|
||||
_container.type == ContainerType.docker ? ContainerType.podman : ContainerType.docker,
|
||||
);
|
||||
ref
|
||||
.read(_provider.notifier)
|
||||
.setType(
|
||||
containerState.type == ContainerType.docker ? ContainerType.podman : ContainerType.docker,
|
||||
);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:server_box/core/chan.dart';
|
||||
import 'package:server_box/core/sync.dart';
|
||||
import 'package:server_box/data/model/app/tab.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/res/build_data.dart';
|
||||
@@ -10,16 +12,16 @@ import 'package:server_box/data/res/url.dart';
|
||||
import 'package:server_box/view/page/setting/entry.dart';
|
||||
import 'package:wakelock_plus/wakelock_plus.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
class HomePage extends ConsumerStatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
ConsumerState<HomePage> createState() => _HomePageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: HomePage.new, path: '/');
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage>
|
||||
class _HomePageState extends ConsumerState<HomePage>
|
||||
with AutomaticKeepAliveClientMixin, AfterLayoutMixin, WidgetsBindingObserver {
|
||||
late final PageController _pageController;
|
||||
|
||||
@@ -29,11 +31,14 @@ class _HomePageState extends State<HomePage>
|
||||
bool _shouldAuth = false;
|
||||
DateTime? _pausedTime;
|
||||
|
||||
late final _notifier = ref.read(serverNotifierProvider.notifier);
|
||||
late final _provider = ref.read(serverNotifierProvider);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
ServerProvider.closeServer();
|
||||
Future(() => _notifier.closeServer());
|
||||
_pageController.dispose();
|
||||
WakelockPlus.disable();
|
||||
|
||||
@@ -76,8 +81,9 @@ class _HomePageState extends State<HomePage>
|
||||
_goAuth();
|
||||
}
|
||||
}
|
||||
if (!ServerProvider.isAutoRefreshOn) {
|
||||
ServerProvider.startAutoRefresh();
|
||||
final serverNotifier = _notifier;
|
||||
if (_provider.autoRefreshTimer == null) {
|
||||
serverNotifier.startAutoRefresh();
|
||||
}
|
||||
MethodChans.updateHomeWidget();
|
||||
break;
|
||||
@@ -92,7 +98,7 @@ class _HomePageState extends State<HomePage>
|
||||
// }
|
||||
} else {
|
||||
//Pros.server.setDisconnected();
|
||||
ServerProvider.stopAutoRefresh();
|
||||
_notifier.stopAutoRefresh();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
@@ -194,7 +200,9 @@ class _HomePageState extends State<HomePage>
|
||||
AppUpdateIface.doUpdate(build: BuildData.build, url: Urls.updateCfg, context: context);
|
||||
}
|
||||
MethodChans.updateHomeWidget();
|
||||
await ServerProvider.refresh();
|
||||
await _notifier.refresh();
|
||||
|
||||
bakSync.sync(milliDelay: 1000);
|
||||
}
|
||||
|
||||
// Future<void> _reqNotiPerm() async {
|
||||
@@ -202,7 +210,6 @@ class _HomePageState extends State<HomePage>
|
||||
// final suc = await PermUtils.request(Permission.notification);
|
||||
// if (!suc) {
|
||||
// final noNotiPerm = Stores.setting.noNotiPerm;
|
||||
// if (noNotiPerm.fetch()) return;
|
||||
// context.showRoundDialog(
|
||||
// title: l10n.error,
|
||||
// child: Text(l10n.noNotiPerm),
|
||||
@@ -212,6 +219,7 @@ class _HomePageState extends State<HomePage>
|
||||
// noNotiPerm.put(true);
|
||||
// context.pop();
|
||||
// },
|
||||
// if (noNotiPerm.fetch()) return;
|
||||
// child: Text(l10n.ok),
|
||||
// ),
|
||||
// ],
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/ping_result.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
@@ -9,16 +10,16 @@ import 'package:server_box/data/provider/server.dart';
|
||||
/// Only permit ipv4 / ipv6 / domain chars
|
||||
final targetReg = RegExp(r'[a-zA-Z0-9\.-_:]+');
|
||||
|
||||
class PingPage extends StatefulWidget {
|
||||
class PingPage extends ConsumerStatefulWidget {
|
||||
const PingPage({super.key});
|
||||
|
||||
@override
|
||||
State<PingPage> createState() => _PingPageState();
|
||||
ConsumerState<PingPage> createState() => _PingPageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: PingPage.new, path: '/ping');
|
||||
}
|
||||
|
||||
class _PingPageState extends State<PingPage> with AutomaticKeepAliveClientMixin {
|
||||
class _PingPageState extends ConsumerState<PingPage> with AutomaticKeepAliveClientMixin {
|
||||
late TextEditingController _textEditingController;
|
||||
final _results = ValueNotifier(<PingResult>[]);
|
||||
bool get isInit => _results.value.isEmpty;
|
||||
@@ -129,7 +130,7 @@ class _PingPageState extends State<PingPage> with AutomaticKeepAliveClientMixin
|
||||
return;
|
||||
}
|
||||
|
||||
if (ServerProvider.serverOrder.value.isEmpty) {
|
||||
if (ref.read(serverNotifierProvider).serverOrder.isEmpty) {
|
||||
context.showSnackBar(l10n.pingNoServer);
|
||||
return;
|
||||
}
|
||||
@@ -141,13 +142,13 @@ class _PingPageState extends State<PingPage> with AutomaticKeepAliveClientMixin
|
||||
}
|
||||
|
||||
await Future.wait(
|
||||
ServerProvider.servers.values.map((v) async {
|
||||
final e = v.value;
|
||||
if (e.client == null) {
|
||||
ref.read(serverNotifierProvider).servers.values.map((spi) async {
|
||||
final serverState = ref.read(individualServerNotifierProvider(spi.id));
|
||||
if (serverState.client == null) {
|
||||
return;
|
||||
}
|
||||
final result = await e.client!.run('ping -c 3 $target').string;
|
||||
_results.value.add(PingResult.parse(e.spi.name, result));
|
||||
final result = await serverState.client!.run('ping -c 3 $target').string;
|
||||
_results.value.add(PingResult.parse(spi.name, result));
|
||||
// [ValueNotifier] only notify when value is changed
|
||||
// But we just add a element to list without changing the list itself
|
||||
// So we need to notify manually
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'package:computer/computer.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/utils/server.dart';
|
||||
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||
@@ -17,17 +18,17 @@ final class PrivateKeyEditPageArgs {
|
||||
const PrivateKeyEditPageArgs({this.pki});
|
||||
}
|
||||
|
||||
class PrivateKeyEditPage extends StatefulWidget {
|
||||
class PrivateKeyEditPage extends ConsumerStatefulWidget {
|
||||
final PrivateKeyEditPageArgs? args;
|
||||
const PrivateKeyEditPage({super.key, this.args});
|
||||
|
||||
@override
|
||||
State<PrivateKeyEditPage> createState() => _PrivateKeyEditPageState();
|
||||
ConsumerState<PrivateKeyEditPage> createState() => _PrivateKeyEditPageState();
|
||||
|
||||
static const route = AppRoute(page: PrivateKeyEditPage.new, path: '/private_key/edit');
|
||||
}
|
||||
|
||||
class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
||||
class _PrivateKeyEditPageState extends ConsumerState<PrivateKeyEditPage> {
|
||||
final _nameController = TextEditingController();
|
||||
final _keyController = TextEditingController();
|
||||
final _pwdController = TextEditingController();
|
||||
@@ -39,6 +40,8 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
||||
|
||||
final _loading = ValueNotifier<Widget?>(null);
|
||||
|
||||
late final _notifier = ref.read(privateKeyNotifierProvider.notifier);
|
||||
|
||||
PrivateKeyInfo? get pki => widget.args?.pki;
|
||||
|
||||
@override
|
||||
@@ -94,7 +97,7 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
||||
child: Text(libL10n.askContinue('${libL10n.delete} ${l10n.privateKey}(${pki.id})')),
|
||||
actions: Btn.ok(
|
||||
onTap: () {
|
||||
PrivateKeyProvider.delete(pki);
|
||||
_notifier.delete(pki);
|
||||
context.pop();
|
||||
context.pop();
|
||||
},
|
||||
@@ -196,9 +199,9 @@ class _PrivateKeyEditPageState extends State<PrivateKeyEditPage> {
|
||||
final pki = PrivateKeyInfo(id: name, key: decrypted);
|
||||
final originPki = this.pki;
|
||||
if (originPki != null) {
|
||||
PrivateKeyProvider.update(originPki, pki);
|
||||
_notifier.update(originPki, pki);
|
||||
} else {
|
||||
PrivateKeyProvider.add(pki);
|
||||
_notifier.add(pki);
|
||||
}
|
||||
} catch (e) {
|
||||
context.showSnackBar(e.toString());
|
||||
|
||||
@@ -3,22 +3,23 @@ import 'dart:io';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/private_key_info.dart';
|
||||
import 'package:server_box/data/provider/private_key.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:server_box/view/page/private_key/edit.dart';
|
||||
|
||||
class PrivateKeysListPage extends StatefulWidget {
|
||||
class PrivateKeysListPage extends ConsumerStatefulWidget {
|
||||
const PrivateKeysListPage({super.key});
|
||||
|
||||
@override
|
||||
State<PrivateKeysListPage> createState() => _PrivateKeyListState();
|
||||
ConsumerState<PrivateKeysListPage> createState() => _PrivateKeyListState();
|
||||
|
||||
static const route = AppRouteNoArg(page: PrivateKeysListPage.new, path: '/private_key');
|
||||
}
|
||||
|
||||
class _PrivateKeyListState extends State<PrivateKeysListPage> with AfterLayoutMixin {
|
||||
class _PrivateKeyListState extends ConsumerState<PrivateKeysListPage> with AfterLayoutMixin {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -31,14 +32,15 @@ class _PrivateKeyListState extends State<PrivateKeysListPage> with AfterLayoutMi
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return PrivateKeyProvider.pkis.listenVal((pkis) {
|
||||
if (pkis.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty));
|
||||
}
|
||||
final privateKeyState = ref.watch(privateKeyNotifierProvider);
|
||||
final pkis = privateKeyState.keys;
|
||||
|
||||
if (pkis.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty));
|
||||
}
|
||||
|
||||
final children = pkis.map(_buildKeyItem).toList();
|
||||
return AutoMultiList(children: children);
|
||||
});
|
||||
final children = pkis.map(_buildKeyItem).toList();
|
||||
return AutoMultiList(children: children);
|
||||
}
|
||||
|
||||
Widget _buildKeyItem(PrivateKeyInfo item) {
|
||||
|
||||
@@ -3,25 +3,26 @@ import 'dart:async';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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/shell_func.dart';
|
||||
import 'package:server_box/data/model/server/proc.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class ProcessPage extends StatefulWidget {
|
||||
class ProcessPage extends ConsumerStatefulWidget {
|
||||
final SpiRequiredArgs args;
|
||||
|
||||
const ProcessPage({super.key, required this.args});
|
||||
|
||||
@override
|
||||
State<ProcessPage> createState() => _ProcessPageState();
|
||||
ConsumerState<ProcessPage> createState() => _ProcessPageState();
|
||||
|
||||
static const route = AppRouteArg(page: ProcessPage.new, path: '/process');
|
||||
}
|
||||
|
||||
class _ProcessPageState extends State<ProcessPage> {
|
||||
class _ProcessPageState extends ConsumerState<ProcessPage> {
|
||||
late Timer _timer;
|
||||
late MediaQueryData _media;
|
||||
|
||||
@@ -36,6 +37,8 @@ class _ProcessPageState extends State<ProcessPage> {
|
||||
ProcSortMode _procSortMode = ProcSortMode.cpu;
|
||||
List<ProcSortMode> _sortModes = List.from(ProcSortMode.values);
|
||||
|
||||
late final _provider = individualServerNotifierProvider(widget.args.spi.id);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
@@ -45,7 +48,8 @@ class _ProcessPageState extends State<ProcessPage> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_client = widget.args.spi.server?.value.client;
|
||||
final serverState = ref.read(_provider);
|
||||
_client = serverState.client;
|
||||
final duration = Duration(seconds: Stores.setting.serverStatusUpdateInterval.fetch());
|
||||
_timer = Timer.periodic(duration, (_) => _refresh());
|
||||
}
|
||||
@@ -58,9 +62,10 @@ class _ProcessPageState extends State<ProcessPage> {
|
||||
|
||||
Future<void> _refresh() async {
|
||||
if (mounted) {
|
||||
final systemType = widget.args.spi.server?.value.status.system;
|
||||
final serverState = ref.read(_provider);
|
||||
final systemType = serverState.status.system;
|
||||
final result = await _client
|
||||
?.run(ShellFunc.process.exec(widget.args.spi.id, systemType: systemType))
|
||||
?.run(ShellFunc.process.exec(widget.args.spi.id, systemType: systemType, customDir: null))
|
||||
.string;
|
||||
if (result == null || result.isEmpty) {
|
||||
context.showSnackBar(libL10n.empty);
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/pve.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
@@ -15,29 +16,30 @@ final class PvePageArgs {
|
||||
const PvePageArgs({required this.spi});
|
||||
}
|
||||
|
||||
final class PvePage extends StatefulWidget {
|
||||
final class PvePage extends ConsumerStatefulWidget {
|
||||
final PvePageArgs args;
|
||||
|
||||
const PvePage({super.key, required this.args});
|
||||
|
||||
@override
|
||||
State<PvePage> createState() => _PvePageState();
|
||||
ConsumerState<PvePage> createState() => _PvePageState();
|
||||
|
||||
static const route = AppRouteArg<void, PvePageArgs>(page: PvePage.new, path: '/pve');
|
||||
}
|
||||
|
||||
const _kHorziPadding = 11.0;
|
||||
|
||||
final class _PvePageState extends State<PvePage> {
|
||||
late final _pve = PveProvider(spi: widget.args.spi);
|
||||
final class _PvePageState extends ConsumerState<PvePage> {
|
||||
late MediaQueryData _media;
|
||||
Timer? _timer;
|
||||
|
||||
late final _provider = pveNotifierProvider(widget.args.spi);
|
||||
late final _notifier = ref.read(_provider.notifier);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_timer?.cancel();
|
||||
_pve.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -55,43 +57,34 @@ final class _PvePageState extends State<PvePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final pveState = ref.watch(_provider);
|
||||
|
||||
// If there is an error, stop the timer
|
||||
if (pveState.error != null) {
|
||||
_timer?.cancel();
|
||||
}
|
||||
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(
|
||||
title: TwoLineText(up: 'PVE', down: widget.args.spi.name),
|
||||
actions: [
|
||||
ValBuilder(
|
||||
listenable: _pve.err,
|
||||
builder: (val) => val == null
|
||||
? UIs.placeholder
|
||||
: Btn.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onTap: () {
|
||||
_pve.err.value = null;
|
||||
_pve.list();
|
||||
_initRefreshTimer();
|
||||
},
|
||||
),
|
||||
),
|
||||
pveState.error == null
|
||||
? UIs.placeholder
|
||||
: Btn.icon(
|
||||
icon: const Icon(Icons.refresh),
|
||||
onTap: () {
|
||||
_notifier.list();
|
||||
_initRefreshTimer();
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
body: ValBuilder(
|
||||
listenable: _pve.err,
|
||||
builder: (val) {
|
||||
if (val != null) {
|
||||
_timer?.cancel();
|
||||
return Padding(
|
||||
body: pveState.error != null
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(13),
|
||||
child: Center(child: Text(val)),
|
||||
);
|
||||
}
|
||||
return ValBuilder(
|
||||
listenable: _pve.data,
|
||||
builder: (val) {
|
||||
return _buildBody(val);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
child: Center(child: Text(pveState.error.toString())),
|
||||
)
|
||||
: _buildBody(pveState.data),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -342,7 +335,7 @@ final class _PvePageState extends State<PvePage> {
|
||||
if (!item.available) {
|
||||
return Btn.icon(
|
||||
icon: const Icon(Icons.play_arrow, color: Colors.grey),
|
||||
onTap: () => _onCtrl(_pve.start, l10n.start, item),
|
||||
onTap: () => _onCtrl(l10n.start, item, () => _notifier.start(item.node, item.id)),
|
||||
);
|
||||
}
|
||||
return Row(
|
||||
@@ -350,17 +343,17 @@ final class _PvePageState extends State<PvePage> {
|
||||
Btn.icon(
|
||||
icon: const Icon(Icons.stop, color: Colors.grey, size: 20),
|
||||
padding: pad,
|
||||
onTap: () => _onCtrl(_pve.stop, l10n.stop, item),
|
||||
onTap: () => _onCtrl(l10n.stop, item, () => _notifier.stop(item.node, item.id)),
|
||||
),
|
||||
Btn.icon(
|
||||
icon: const Icon(Icons.refresh, color: Colors.grey, size: 20),
|
||||
padding: pad,
|
||||
onTap: () => _onCtrl(_pve.reboot, l10n.reboot, item),
|
||||
onTap: () => _onCtrl(l10n.reboot, item, () => _notifier.reboot(item.node, item.id)),
|
||||
),
|
||||
Btn.icon(
|
||||
icon: const Icon(Icons.power_off, color: Colors.grey, size: 20),
|
||||
padding: pad,
|
||||
onTap: () => _onCtrl(_pve.shutdown, l10n.shutdown, item),
|
||||
onTap: () => _onCtrl(l10n.shutdown, item, () => _notifier.shutdown(item.node, item.id)),
|
||||
),
|
||||
],
|
||||
);
|
||||
@@ -368,7 +361,7 @@ final class _PvePageState extends State<PvePage> {
|
||||
}
|
||||
|
||||
extension on _PvePageState {
|
||||
void _onCtrl(PveCtrlFunc func, String action, PveCtrlIface item) async {
|
||||
void _onCtrl(String action, PveCtrlIface item, Future<bool> Function() func) async {
|
||||
final sure = await context.showRoundDialog<bool>(
|
||||
title: libL10n.attention,
|
||||
child: Text(libL10n.askContinue('$action ${item.id}')),
|
||||
@@ -376,7 +369,7 @@ extension on _PvePageState {
|
||||
);
|
||||
if (sure != true) return;
|
||||
|
||||
final (suc, err) = await context.showLoadingDialog(fn: () => func(item.node, item.id));
|
||||
final (suc, err) = await context.showLoadingDialog(fn: func);
|
||||
if (suc == true) {
|
||||
context.showSnackBar(libL10n.success);
|
||||
} else {
|
||||
@@ -384,28 +377,40 @@ extension on _PvePageState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Add PveNode if [PveProvider.onlyOneNode] is false
|
||||
/// Add PveNode if only one node exists
|
||||
String _wrapNodeName(PveCtrlIface item) {
|
||||
if (_pve.onlyOneNode) {
|
||||
final pveState = ref.read(_provider);
|
||||
if (pveState.data?.onlyOneNode ?? false) {
|
||||
return item.name;
|
||||
}
|
||||
return '${item.node} / ${item.name}';
|
||||
}
|
||||
|
||||
void _initRefreshTimer() {
|
||||
_timer?.cancel();
|
||||
_timer = Timer.periodic(Duration(seconds: Stores.setting.serverStatusUpdateInterval.fetch()), (_) {
|
||||
if (mounted) {
|
||||
_pve.list();
|
||||
_notifier.list();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _afterInit() async {
|
||||
await _pve.connected.future;
|
||||
if (_pve.release != null && _pve.release!.compareTo('8.0') < 0) {
|
||||
if (mounted) {
|
||||
context.showSnackBar(l10n.pveVersionLow);
|
||||
// Wait for the PVE state to be connected
|
||||
while (mounted) {
|
||||
final pveState = ref.read(_provider);
|
||||
if (pveState.isConnected) {
|
||||
if (pveState.release != null && pveState.release!.compareTo('8.0') < 0) {
|
||||
if (mounted) {
|
||||
context.showSnackBar(l10n.pveVersionLow);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (pveState.error != null) {
|
||||
break; // Skip if there is an error
|
||||
}
|
||||
await Future.delayed(const Duration(milliseconds: 100));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,8 +3,10 @@ import 'package:fl_chart/fl_chart.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/extension/server.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/app/server_detail_card.dart';
|
||||
@@ -16,28 +18,28 @@ import 'package:server_box/data/model/server/disk_smart.dart';
|
||||
import 'package:server_box/data/model/server/net_speed.dart';
|
||||
import 'package:server_box/data/model/server/nvdia.dart';
|
||||
import 'package:server_box/data/model/server/sensors.dart';
|
||||
import 'package:server_box/data/model/server/server.dart';
|
||||
import 'package:server_box/data/model/server/server.dart' as server_model;
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
import 'package:server_box/view/page/pve.dart';
|
||||
import 'package:server_box/view/page/server/edit.dart';
|
||||
import 'package:server_box/view/page/server/logo.dart';
|
||||
import 'package:server_box/view/widget/server_func_btns.dart';
|
||||
|
||||
part 'misc.dart';
|
||||
|
||||
class ServerDetailPage extends StatefulWidget {
|
||||
class ServerDetailPage extends ConsumerStatefulWidget {
|
||||
final SpiRequiredArgs args;
|
||||
const ServerDetailPage({super.key, required this.args});
|
||||
|
||||
@override
|
||||
State<ServerDetailPage> createState() => _ServerDetailPageState();
|
||||
ConsumerState<ServerDetailPage> createState() => _ServerDetailPageState();
|
||||
|
||||
static const route = AppRouteArg(page: ServerDetailPage.new, path: '/servers/detail');
|
||||
}
|
||||
|
||||
class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerProviderStateMixin {
|
||||
class _ServerDetailPageState extends ConsumerState<ServerDetailPage> with SingleTickerProviderStateMixin {
|
||||
late final _cardBuildMap = Map.fromIterables(ServerDetailCards.names, [
|
||||
_buildAbout,
|
||||
_buildCPUView,
|
||||
@@ -84,17 +86,17 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = widget.args.spi.server;
|
||||
if (s == null) {
|
||||
final serverState = ref.watch(individualServerNotifierProvider(widget.args.spi.id));
|
||||
if (serverState.client == null) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(),
|
||||
body: Center(child: Text(libL10n.empty)),
|
||||
);
|
||||
}
|
||||
return s.listenVal(_buildMainPage);
|
||||
return _buildMainPage(serverState);
|
||||
}
|
||||
|
||||
Widget _buildMainPage(Server si) {
|
||||
Widget _buildMainPage(ServerState si) {
|
||||
final buildFuncs = !Stores.setting.moveServerFuncs.fetch();
|
||||
final logo = _buildLogo(si);
|
||||
final children = <Widget>[if (logo != null) logo, if (buildFuncs) ServerFuncBtns(spi: si.spi)];
|
||||
@@ -111,7 +113,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
CustomAppBar _buildAppBar(Server si) {
|
||||
CustomAppBar _buildAppBar(ServerState si) {
|
||||
return CustomAppBar(
|
||||
title: Text(
|
||||
si.spi.name,
|
||||
@@ -132,7 +134,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildLogo(Server si) {
|
||||
Widget? _buildLogo(ServerState si) {
|
||||
final logoUrl = si.getLogoUrl(context);
|
||||
|
||||
return Padding(
|
||||
@@ -153,7 +155,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildAbout(Server si) {
|
||||
Widget? _buildAbout(ServerState si) {
|
||||
final ss = si.status;
|
||||
return ExpandTile(
|
||||
key: ValueKey(ss.more.hashCode), // Use hashCode to avoid perf issue
|
||||
@@ -178,7 +180,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
).cardx;
|
||||
}
|
||||
|
||||
Widget? _buildCPUView(Server si) {
|
||||
Widget? _buildCPUView(ServerState si) {
|
||||
final ss = si.status;
|
||||
final percent = ss.cpu.usedPercent(coreIdx: 0).toInt();
|
||||
final details = [
|
||||
@@ -305,7 +307,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
return children;
|
||||
}
|
||||
|
||||
Widget _buildCPUChart(ServerStatus ss) {
|
||||
Widget _buildCPUChart(server_model.ServerStatus ss) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 17, vertical: 13),
|
||||
child: LayoutBuilder(
|
||||
@@ -335,7 +337,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildMemView(Server si) {
|
||||
Widget? _buildMemView(ServerState si) {
|
||||
final ss = si.status;
|
||||
final free = ss.mem.free / ss.mem.total * 100;
|
||||
final avail = ss.mem.availPercent * 100;
|
||||
@@ -376,7 +378,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
).cardx;
|
||||
}
|
||||
|
||||
Widget? _buildSwapView(Server si) {
|
||||
Widget? _buildSwapView(ServerState si) {
|
||||
final ss = si.status;
|
||||
if (ss.swap.total == 0) return null;
|
||||
|
||||
@@ -408,7 +410,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
).cardx;
|
||||
}
|
||||
|
||||
Widget? _buildGpuView(Server si) {
|
||||
Widget? _buildGpuView(ServerState si) {
|
||||
final ss = si.status;
|
||||
final hasNvidia = ss.nvidia != null && ss.nvidia!.isNotEmpty;
|
||||
final hasAmd = ss.amd != null && ss.amd!.isNotEmpty;
|
||||
@@ -532,7 +534,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildDiskView(Server si) {
|
||||
Widget? _buildDiskView(ServerState si) {
|
||||
final ss = si.status;
|
||||
final children = <Widget>[];
|
||||
|
||||
@@ -553,7 +555,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
).cardx;
|
||||
}
|
||||
|
||||
Widget _buildDiskItemWithHierarchy(Disk disk, ServerStatus ss, int depth) {
|
||||
Widget _buildDiskItemWithHierarchy(Disk disk, server_model.ServerStatus ss, int depth) {
|
||||
// Create a list to hold this disk and its children
|
||||
final items = <Widget>[];
|
||||
|
||||
@@ -570,7 +572,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
return Column(children: items);
|
||||
}
|
||||
|
||||
Widget _buildDiskItem(Disk disk, ServerStatus ss, int depth) {
|
||||
Widget _buildDiskItem(Disk disk, server_model.ServerStatus ss, int depth) {
|
||||
final (read, write) = ss.diskIO.getSpeed(disk.path);
|
||||
final text = () {
|
||||
final use = '${l10n.used} ${disk.used.kb2Str} / ${disk.size.kb2Str}';
|
||||
@@ -625,7 +627,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildDiskSmart(Server si) {
|
||||
Widget? _buildDiskSmart(ServerState si) {
|
||||
final smarts = si.status.diskSmart;
|
||||
if (smarts.isEmpty) return null;
|
||||
return CardX(
|
||||
@@ -770,7 +772,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildNetView(Server si) {
|
||||
Widget? _buildNetView(ServerState si) {
|
||||
final ss = si.status;
|
||||
final ns = ss.netSpeed;
|
||||
final children = <Widget>[];
|
||||
@@ -847,7 +849,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildTemperature(Server si) {
|
||||
Widget? _buildTemperature(ServerState si) {
|
||||
final ss = si.status;
|
||||
if (ss.temps.isEmpty) return null;
|
||||
|
||||
@@ -879,7 +881,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildBatteries(Server si) {
|
||||
Widget? _buildBatteries(ServerState si) {
|
||||
final ss = si.status;
|
||||
if (ss.batteries.isEmpty) return null;
|
||||
|
||||
@@ -914,7 +916,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildSensors(Server si) {
|
||||
Widget? _buildSensors(ServerState si) {
|
||||
final ss = si.status;
|
||||
if (ss.sensors.isEmpty) return UIs.placeholder;
|
||||
return CardX(
|
||||
@@ -967,7 +969,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildPve(Server si) {
|
||||
Widget? _buildPve(ServerState si) {
|
||||
final addr = si.spi.custom?.pveAddr;
|
||||
if (addr == null || addr.isEmpty) return null;
|
||||
return CardX(
|
||||
@@ -980,7 +982,7 @@ class _ServerDetailPageState extends State<ServerDetailPage> with SingleTickerPr
|
||||
);
|
||||
}
|
||||
|
||||
Widget? _buildCustomCmd(Server si) {
|
||||
Widget? _buildCustomCmd(ServerState si) {
|
||||
final ss = si.status;
|
||||
if (ss.customCmds.isEmpty) return null;
|
||||
return CardX(
|
||||
|
||||
@@ -3,12 +3,12 @@ import 'dart:convert';
|
||||
import 'package:choice/choice.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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';
|
||||
import 'package:server_box/data/model/server/system.dart';
|
||||
import 'package:server_box/data/model/server/wol_cfg.dart';
|
||||
@@ -17,7 +17,7 @@ import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/store/server.dart';
|
||||
import 'package:server_box/view/page/private_key/edit.dart';
|
||||
|
||||
class ServerEditPage extends StatefulWidget {
|
||||
class ServerEditPage extends ConsumerStatefulWidget {
|
||||
final SpiRequiredArgs? args;
|
||||
|
||||
const ServerEditPage({super.key, this.args});
|
||||
@@ -25,10 +25,10 @@ class ServerEditPage extends StatefulWidget {
|
||||
static const route = AppRoute<bool, SpiRequiredArgs>(page: ServerEditPage.new, path: '/servers/edit');
|
||||
|
||||
@override
|
||||
State<ServerEditPage> createState() => _ServerEditPageState();
|
||||
ConsumerState<ServerEditPage> createState() => _ServerEditPageState();
|
||||
}
|
||||
|
||||
class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
class _ServerEditPageState extends ConsumerState<ServerEditPage> with AfterLayoutMixin {
|
||||
late final spi = widget.args?.spi;
|
||||
final _nameController = TextEditingController();
|
||||
final _ipController = TextEditingController();
|
||||
@@ -167,7 +167,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
hint: 'root',
|
||||
suggestion: false,
|
||||
),
|
||||
TagTile(tags: _tags, allTags: ServerProvider.tags.value).cardx,
|
||||
TagTile(tags: _tags, allTags: ref.watch(serverNotifierProvider).tags).cardx,
|
||||
ListTile(
|
||||
title: Text(l10n.autoConnect),
|
||||
trailing: _autoConnect.listenVal(
|
||||
@@ -227,12 +227,14 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
}
|
||||
|
||||
Widget _buildKeyAuth() {
|
||||
return PrivateKeyProvider.pkis.listenVal((pkis) {
|
||||
final tiles = List<Widget>.generate(pkis.length, (index) {
|
||||
final e = pkis[index];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.only(left: 10, right: 15),
|
||||
leading: Radio<int>(value: index),
|
||||
final privateKeyState = ref.watch(privateKeyNotifierProvider);
|
||||
final pkis = privateKeyState.keys;
|
||||
|
||||
final tiles = List<Widget>.generate(pkis.length, (index) {
|
||||
final e = pkis[index];
|
||||
return ListTile(
|
||||
contentPadding: const EdgeInsets.only(left: 10, right: 15),
|
||||
leading: Radio<int>(value: index),
|
||||
title: Text(e.id, textAlign: TextAlign.start),
|
||||
subtitle: Text(e.type ?? l10n.unknown, textAlign: TextAlign.start, style: UIs.textGrey),
|
||||
trailing: Btn.icon(
|
||||
@@ -254,7 +256,6 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
onChanged: (val) => _keyIdx.value = val,
|
||||
child: _keyIdx.listenVal((_) => Column(children: tiles)).cardx,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildEnvs() {
|
||||
@@ -485,27 +486,26 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
|
||||
Widget _buildJumpServer() {
|
||||
const padding = EdgeInsets.only(left: 13, right: 13, bottom: 7);
|
||||
final srvs = ServerProvider.servers.values
|
||||
.map((e) => e.value)
|
||||
.where((e) => e.spi.jumpId == null)
|
||||
.where((e) => e.spi.id != spi?.id)
|
||||
final srvs = ref.watch(serverNotifierProvider).servers.values
|
||||
.where((e) => e.jumpId == null)
|
||||
.where((e) => e.id != spi?.id)
|
||||
.toList();
|
||||
final choice = _jumpServer.listenVal((val) {
|
||||
final srv = srvs.firstWhereOrNull((e) => e.id == _jumpServer.value);
|
||||
return Choice<Server>(
|
||||
return Choice<Spi>(
|
||||
multiple: false,
|
||||
clearable: true,
|
||||
value: srv != null ? [srv] : [],
|
||||
builder: (state, _) => Wrap(
|
||||
children: List<Widget>.generate(srvs.length, (index) {
|
||||
final item = srvs[index];
|
||||
return ChoiceChipX<Server>(
|
||||
label: item.spi.name,
|
||||
return ChoiceChipX<Spi>(
|
||||
label: item.name,
|
||||
state: state,
|
||||
value: item,
|
||||
onSelected: (srv, on) {
|
||||
if (on) {
|
||||
_jumpServer.value = srv.spi.id;
|
||||
_jumpServer.value = srv.id;
|
||||
} else {
|
||||
_jumpServer.value = null;
|
||||
}
|
||||
@@ -569,7 +569,7 @@ class _ServerEditPageState extends State<ServerEditPage> with AfterLayoutMixin {
|
||||
actions: Btn.ok(
|
||||
onTap: () async {
|
||||
context.pop();
|
||||
ServerProvider.delServer(spi!.id);
|
||||
ref.read(serverNotifierProvider.notifier).delServer(spi!.id);
|
||||
context.pop(true);
|
||||
},
|
||||
red: true,
|
||||
@@ -705,7 +705,7 @@ extension on _ServerEditPageState {
|
||||
port: int.parse(_portController.text),
|
||||
user: _usernameController.text,
|
||||
pwd: _passwordController.text.selfNotEmptyOrNull,
|
||||
keyId: _keyIdx.value != null ? PrivateKeyProvider.pkis.value.elementAt(_keyIdx.value!).id : null,
|
||||
keyId: _keyIdx.value != null ? ref.read(privateKeyNotifierProvider).keys.elementAt(_keyIdx.value!).id : null,
|
||||
tags: _tags.value.isEmpty ? null : _tags.value.toList(),
|
||||
alterUrl: _altUrlController.text.selfNotEmptyOrNull,
|
||||
autoConnect: _autoConnect.value,
|
||||
@@ -724,9 +724,9 @@ extension on _ServerEditPageState {
|
||||
context.showSnackBar('${l10n.sameIdServerExist}: ${spi.id}');
|
||||
return;
|
||||
}
|
||||
ServerProvider.addServer(spi);
|
||||
ref.read(serverNotifierProvider.notifier).addServer(spi);
|
||||
} else {
|
||||
ServerProvider.updateServer(this.spi!, spi);
|
||||
ref.read(serverNotifierProvider.notifier).updateServer(this.spi!, spi);
|
||||
}
|
||||
|
||||
context.pop();
|
||||
@@ -740,7 +740,7 @@ extension on _ServerEditPageState {
|
||||
if (spi.keyId == null) {
|
||||
_passwordController.text = spi.pwd ?? '';
|
||||
} else {
|
||||
_keyIdx.value = PrivateKeyProvider.pkis.value.indexWhere((e) => e.id == spi.keyId);
|
||||
_keyIdx.value = ref.read(privateKeyNotifierProvider).keys.indexWhere((e) => e.id == spi.keyId);
|
||||
}
|
||||
|
||||
/// List in dart is passed by pointer, so you need to copy it here
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'tab.dart';
|
||||
|
||||
extension on _ServerPageState {
|
||||
Widget _buildServerCardTitle(Server s) {
|
||||
Widget _buildServerCardTitle(ServerState s) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(left: 7, right: 13),
|
||||
child: Row(
|
||||
@@ -17,12 +17,12 @@ extension on _ServerPageState {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildTopRightWidget(Server s) {
|
||||
Widget _buildTopRightWidget(ServerState s) {
|
||||
final (child, onTap) = switch (s.conn) {
|
||||
ServerConn.connecting || ServerConn.loading || ServerConn.connected => (
|
||||
SizedBox.square(
|
||||
dimension: _ServerPageState._kCardHeightMin,
|
||||
child: SizedLoading(_ServerPageState._kCardHeightMin, strokeWidth: 3, padding: 3),
|
||||
child: SizedLoading(_ServerPageState._kCardHeightMin, padding: 3),
|
||||
),
|
||||
null,
|
||||
),
|
||||
@@ -30,16 +30,16 @@ extension on _ServerPageState {
|
||||
const Icon(Icons.refresh, size: 21, color: Colors.grey),
|
||||
() {
|
||||
TryLimiter.reset(s.spi.id);
|
||||
ServerProvider.refresh(spi: s.spi);
|
||||
ref.read(serverNotifierProvider.notifier).refresh(spi: s.spi);
|
||||
},
|
||||
),
|
||||
ServerConn.disconnected => (
|
||||
const Icon(MingCute.link_3_line, size: 19, color: Colors.grey),
|
||||
() => ServerProvider.refresh(spi: s.spi),
|
||||
() => ref.read(serverNotifierProvider.notifier).refresh(spi: s.spi),
|
||||
),
|
||||
ServerConn.finished => (
|
||||
const Icon(MingCute.unlink_2_line, size: 17, color: Colors.grey),
|
||||
() => ServerProvider.closeServer(id: s.spi.id),
|
||||
() => ref.read(serverNotifierProvider.notifier).closeServer(id: s.spi.id),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -51,7 +51,7 @@ extension on _ServerPageState {
|
||||
return InkWell(borderRadius: BorderRadius.circular(7), onTap: onTap, child: wrapped).paddingOnly(left: 5);
|
||||
}
|
||||
|
||||
Widget _buildTopRightText(Server s) {
|
||||
Widget _buildTopRightText(ServerState s) {
|
||||
final hasErr = s.status.err != null;
|
||||
final str = s._getTopRightStr(s.spi);
|
||||
if (str == null) return UIs.placeholder;
|
||||
@@ -106,7 +106,7 @@ ${ss.err?.message ?? 'null'}
|
||||
Widget _buildNet(ServerStatus ss, String id) {
|
||||
final cardNoti = _getCardNoti(id);
|
||||
final type = cardNoti.value.net ?? Stores.setting.netViewType.fetch();
|
||||
final device = ServerProvider.pick(id: id)?.value.spi.custom?.netDev;
|
||||
final device = ref.watch(serverNotifierProvider).servers[id]?.custom?.netDev;
|
||||
final (a, b) = type.build(ss, dev: device);
|
||||
return AnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 377),
|
||||
|
||||
@@ -26,33 +26,31 @@ extension on _ServerPageState {
|
||||
}
|
||||
|
||||
Widget _buildLandscapeBody() {
|
||||
return ServerProvider.serverOrder.listenVal((order) {
|
||||
if (order.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty, textAlign: TextAlign.center));
|
||||
}
|
||||
final serverState = ref.watch(serverNotifierProvider);
|
||||
final order = serverState.serverOrder;
|
||||
|
||||
if (order.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty, textAlign: TextAlign.center));
|
||||
}
|
||||
|
||||
return PageView.builder(
|
||||
itemCount: order.length,
|
||||
itemBuilder: (_, idx) {
|
||||
final id = order[idx];
|
||||
final srv = ServerProvider.pick(id: id);
|
||||
if (srv == null) return UIs.placeholder;
|
||||
return PageView.builder(
|
||||
itemCount: order.length,
|
||||
itemBuilder: (_, idx) {
|
||||
final id = order[idx];
|
||||
final srv = ref.watch(individualServerNotifierProvider(id));
|
||||
|
||||
return srv.listenVal((srv) {
|
||||
final title = _buildServerCardTitle(srv);
|
||||
final List<Widget> children = [title, _buildNormalCard(srv.status, srv.spi)];
|
||||
final title = _buildServerCardTitle(srv);
|
||||
final List<Widget> children = [title, _buildNormalCard(srv.status, srv.spi)];
|
||||
|
||||
return _getCardNoti(id).listenVal((_) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: children,
|
||||
);
|
||||
});
|
||||
});
|
||||
},
|
||||
);
|
||||
});
|
||||
return _getCardNoti(id).listenVal((_) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: children,
|
||||
);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:math' as math;
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:responsive_framework/responsive_framework.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
@@ -31,11 +32,11 @@ part 'landscape.dart';
|
||||
part 'top_bar.dart';
|
||||
part 'utils.dart';
|
||||
|
||||
class ServerPage extends StatefulWidget {
|
||||
class ServerPage extends ConsumerStatefulWidget {
|
||||
const ServerPage({super.key});
|
||||
|
||||
@override
|
||||
State<ServerPage> createState() => _ServerPageState();
|
||||
ConsumerState<ServerPage> createState() => _ServerPageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: ServerPage.new, path: '/servers');
|
||||
}
|
||||
@@ -43,12 +44,14 @@ class ServerPage extends StatefulWidget {
|
||||
const _cardPad = 74.0;
|
||||
const _cardPadSingle = 13.0;
|
||||
|
||||
class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMixin, AfterLayoutMixin {
|
||||
class _ServerPageState extends ConsumerState<ServerPage>
|
||||
with AutomaticKeepAliveClientMixin, AfterLayoutMixin {
|
||||
late double _textFactorDouble;
|
||||
double _offset = 1;
|
||||
late TextScaler _textFactor;
|
||||
|
||||
final _cardsStatus = <String, _CardNotifier>{};
|
||||
late final ValueNotifier<Set<String>> _tags;
|
||||
|
||||
Timer? _timer;
|
||||
|
||||
@@ -64,11 +67,13 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
_scrollController.dispose();
|
||||
_autoHideCtrl.dispose();
|
||||
_tag.dispose();
|
||||
_tags.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_tags = ValueNotifier(ref.read(serverNotifierProvider).tags);
|
||||
_startAvoidJitterTimer();
|
||||
}
|
||||
|
||||
@@ -78,9 +83,14 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
_updateOffset();
|
||||
}
|
||||
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
super.build(context);
|
||||
// Listen to provider changes and update the ValueNotifier
|
||||
ref.listen(serverNotifierProvider, (previous, next) {
|
||||
_tags.value = next.tags;
|
||||
});
|
||||
return OrientationBuilder(
|
||||
builder: (_, orientation) {
|
||||
if (orientation == Orientation.landscape) {
|
||||
@@ -96,7 +106,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
|
||||
Widget _buildScaffold(Widget child) {
|
||||
return Scaffold(
|
||||
appBar: _TopBar(tags: ServerProvider.tags, onTagChanged: (p0) => _tag.value = p0, initTag: _tag.value),
|
||||
appBar: _TopBar(tags: _tags, onTagChanged: (p0) => _tag.value = p0, initTag: _tag.value),
|
||||
body: GestureDetector(
|
||||
behavior: HitTestBehavior.opaque,
|
||||
onTap: _autoHideCtrl.show,
|
||||
@@ -122,22 +132,21 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
|
||||
Widget _buildPortrait() {
|
||||
// final isMobile = ResponsiveBreakpoints.of(context).isMobile;
|
||||
return ServerProvider.serverOrder.listenVal((order) {
|
||||
return _tag.listenVal((val) {
|
||||
final filtered = _filterServers(order);
|
||||
final child = _buildScaffold(_buildBodySmall(filtered: filtered));
|
||||
// if (isMobile) {
|
||||
return child;
|
||||
// }
|
||||
final serverState = ref.watch(serverNotifierProvider);
|
||||
return _tag.listenVal((val) {
|
||||
final filtered = _filterServers(serverState.serverOrder);
|
||||
final child = _buildScaffold(_buildBodySmall(filtered: filtered));
|
||||
// if (isMobile) {
|
||||
return child;
|
||||
// }
|
||||
|
||||
// return SplitView(
|
||||
// controller: _splitViewCtrl,
|
||||
// leftWeight: 1,
|
||||
// rightWeight: 1.3,
|
||||
// initialRight: Center(child: CircularProgressIndicator()),
|
||||
// leftBuilder: (_, __) => child,
|
||||
// );
|
||||
});
|
||||
// return SplitView(
|
||||
// controller: _splitViewCtrl,
|
||||
// leftWeight: 1,
|
||||
// rightWeight: 1.3,
|
||||
// initialRight: Center(child: CircularProgressIndicator()),
|
||||
// leftBuilder: (_, __) => child,
|
||||
// );
|
||||
});
|
||||
}
|
||||
|
||||
@@ -173,10 +182,9 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
// Last item is just spacing
|
||||
if (index == lens) return SizedBox(height: 77);
|
||||
|
||||
final vnode = ServerProvider.pick(id: serversInThisColumn[index]);
|
||||
if (vnode == null) return UIs.placeholder;
|
||||
final individualState = ref.watch(individualServerNotifierProvider(serversInThisColumn[index]));
|
||||
|
||||
return vnode.listenVal(_buildEachServerCard);
|
||||
return _buildEachServerCard(individualState);
|
||||
},
|
||||
),
|
||||
);
|
||||
@@ -186,9 +194,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildEachServerCard(Server? srv) {
|
||||
if (srv == null) return UIs.placeholder;
|
||||
|
||||
Widget _buildEachServerCard(ServerState srv) {
|
||||
return CardX(
|
||||
key: Key(srv.spi.id + _tag.value),
|
||||
child: InkWell(
|
||||
@@ -218,7 +224,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRealServerCard(Server srv) {
|
||||
Widget _buildRealServerCard(ServerState srv) {
|
||||
final id = srv.spi.id;
|
||||
final cardStatus = _getCardNoti(id);
|
||||
final title = _buildServerCardTitle(srv);
|
||||
@@ -255,7 +261,7 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildFlippedCard(Server srv) {
|
||||
Widget _buildFlippedCard(ServerState srv) {
|
||||
const color = Colors.grey;
|
||||
const textStyle = TextStyle(fontSize: 13, color: color);
|
||||
final children = [
|
||||
@@ -332,8 +338,8 @@ class _ServerPageState extends State<ServerPage> with AutomaticKeepAliveClientMi
|
||||
|
||||
@override
|
||||
Future<void> afterFirstLayout(BuildContext context) async {
|
||||
ServerProvider.refresh();
|
||||
ServerProvider.startAutoRefresh();
|
||||
ref.read(serverNotifierProvider.notifier).refresh();
|
||||
ref.read(serverNotifierProvider.notifier).startAutoRefresh();
|
||||
}
|
||||
|
||||
static const _kCardHeightMin = 23.0;
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
part of 'tab.dart';
|
||||
|
||||
extension _Actions on _ServerPageState {
|
||||
void _onTapCard(Server srv) {
|
||||
void _onTapCard(ServerState srv) {
|
||||
if (srv.canViewDetails) {
|
||||
// _splitViewCtrl.replace(ServerDetailPage(
|
||||
// key: ValueKey(srv.spi.id),
|
||||
@@ -19,7 +19,7 @@ extension _Actions on _ServerPageState {
|
||||
}
|
||||
}
|
||||
|
||||
void _onLongPressCard(Server srv) {
|
||||
void _onLongPressCard(ServerState srv) {
|
||||
if (srv.conn == ServerConn.finished) {
|
||||
final id = srv.spi.id;
|
||||
final cardStatus = _getCardNoti(id);
|
||||
@@ -42,7 +42,7 @@ extension _Actions on _ServerPageState {
|
||||
}
|
||||
|
||||
extension _Operation on _ServerPageState {
|
||||
void _onTapSuspend(Server srv) {
|
||||
void _onTapSuspend(ServerState srv) {
|
||||
_askFor(
|
||||
func: () async {
|
||||
if (Stores.setting.showSuspendTip.fetch()) {
|
||||
@@ -50,7 +50,7 @@ extension _Operation on _ServerPageState {
|
||||
Stores.setting.showSuspendTip.put(false);
|
||||
}
|
||||
srv.client?.execWithPwd(
|
||||
ShellFunc.suspend.exec(srv.spi.id, systemType: srv.status.system),
|
||||
ShellFunc.suspend.exec(srv.spi.id, systemType: srv.status.system, customDir: null),
|
||||
context: context,
|
||||
id: srv.id,
|
||||
);
|
||||
@@ -60,10 +60,10 @@ extension _Operation on _ServerPageState {
|
||||
);
|
||||
}
|
||||
|
||||
void _onTapShutdown(Server srv) {
|
||||
void _onTapShutdown(ServerState srv) {
|
||||
_askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.shutdown.exec(srv.spi.id, systemType: srv.status.system),
|
||||
ShellFunc.shutdown.exec(srv.spi.id, systemType: srv.status.system, customDir: null),
|
||||
context: context,
|
||||
id: srv.id,
|
||||
),
|
||||
@@ -72,10 +72,10 @@ extension _Operation on _ServerPageState {
|
||||
);
|
||||
}
|
||||
|
||||
void _onTapReboot(Server srv) {
|
||||
void _onTapReboot(ServerState srv) {
|
||||
_askFor(
|
||||
func: () => srv.client?.execWithPwd(
|
||||
ShellFunc.reboot.exec(srv.spi.id, systemType: srv.status.system),
|
||||
ShellFunc.reboot.exec(srv.spi.id, systemType: srv.status.system, customDir: null),
|
||||
context: context,
|
||||
id: srv.id,
|
||||
),
|
||||
@@ -84,7 +84,7 @@ extension _Operation on _ServerPageState {
|
||||
);
|
||||
}
|
||||
|
||||
void _onTapEdit(Server srv) {
|
||||
void _onTapEdit(ServerState srv) {
|
||||
if (srv.canViewDetails) {
|
||||
ServerDetailPage.route.go(context, SpiRequiredArgs(srv.spi));
|
||||
} else {
|
||||
@@ -98,7 +98,7 @@ extension _Utils on _ServerPageState {
|
||||
final tag = _tag.value;
|
||||
if (tag == TagSwitcher.kDefaultTag) return order;
|
||||
return order.where((e) {
|
||||
final tags = ServerProvider.pick(id: e)?.value.spi.tags;
|
||||
final tags = ref.read(serverNotifierProvider).servers[e]?.tags;
|
||||
if (tags == null) return false;
|
||||
return tags.contains(tag);
|
||||
}).toList();
|
||||
@@ -160,7 +160,7 @@ extension _Utils on _ServerPageState {
|
||||
}
|
||||
}
|
||||
|
||||
extension _ServerX on Server {
|
||||
extension _ServerX on ServerState {
|
||||
String? _getTopRightStr(Spi spi) {
|
||||
if (status.err != null) {
|
||||
return l10n.viewErr;
|
||||
|
||||
@@ -46,7 +46,7 @@ extension _Server on _AppSettingsPageState {
|
||||
onTap: () async {
|
||||
final keys = Stores.server.keys();
|
||||
final names = Map.fromEntries(
|
||||
keys.map((e) => MapEntry(e, ServerProvider.pick(id: e)?.value.spi.name ?? e)),
|
||||
keys.map((e) => MapEntry(e, ref.read(serverNotifierProvider).servers[e]?.name ?? e)),
|
||||
);
|
||||
final deleteKeys = await context.showPickDialog<String>(
|
||||
clearable: true,
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_highlight/theme_map.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/app/net_view.dart';
|
||||
@@ -35,16 +36,16 @@ part 'entries/ssh.dart';
|
||||
|
||||
const _kIconSize = 23.0;
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
class SettingsPage extends ConsumerStatefulWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
static const route = AppRouteNoArg(page: SettingsPage.new, path: '/settings');
|
||||
|
||||
@override
|
||||
State<SettingsPage> createState() => _SettingsPageState();
|
||||
ConsumerState<SettingsPage> createState() => _SettingsPageState();
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> with SingleTickerProviderStateMixin {
|
||||
class _SettingsPageState extends ConsumerState<SettingsPage> with SingleTickerProviderStateMixin {
|
||||
late final _tabCtrl = TabController(length: SettingsTabs.values.length, vsync: this);
|
||||
|
||||
@override
|
||||
@@ -98,14 +99,14 @@ class _SettingsPageState extends State<SettingsPage> with SingleTickerProviderSt
|
||||
}
|
||||
}
|
||||
|
||||
final class AppSettingsPage extends StatefulWidget {
|
||||
final class AppSettingsPage extends ConsumerStatefulWidget {
|
||||
const AppSettingsPage({super.key});
|
||||
|
||||
@override
|
||||
State<AppSettingsPage> createState() => _AppSettingsPageState();
|
||||
ConsumerState<AppSettingsPage> createState() => _AppSettingsPageState();
|
||||
}
|
||||
|
||||
final class _AppSettingsPageState extends State<AppSettingsPage> {
|
||||
final class _AppSettingsPageState extends ConsumerState<AppSettingsPage> {
|
||||
final _setting = Stores.setting;
|
||||
|
||||
late final _sshOpacityCtrl = TextEditingController(text: _setting.sshBgOpacity.fetch().toString());
|
||||
|
||||
@@ -1,21 +1,22 @@
|
||||
import 'dart:ui';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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/provider/server.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
|
||||
class ServerOrderPage extends StatefulWidget {
|
||||
class ServerOrderPage extends ConsumerStatefulWidget {
|
||||
const ServerOrderPage({super.key});
|
||||
|
||||
@override
|
||||
State<ServerOrderPage> createState() => _ServerOrderPageState();
|
||||
ConsumerState<ServerOrderPage> createState() => _ServerOrderPageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: ServerOrderPage.new, path: '/settings/order/server');
|
||||
}
|
||||
|
||||
class _ServerOrderPageState extends State<ServerOrderPage> {
|
||||
class _ServerOrderPageState extends ConsumerState<ServerOrderPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -41,25 +42,27 @@ class _ServerOrderPageState extends State<ServerOrderPage> {
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
final orders = ServerProvider.serverOrder;
|
||||
return orders.listenVal((order) {
|
||||
if (order.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty));
|
||||
}
|
||||
return ReorderableListView.builder(
|
||||
footer: const SizedBox(height: 77),
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
setState(() {
|
||||
orders.value.move(oldIndex, newIndex, property: Stores.setting.serverOrder);
|
||||
});
|
||||
},
|
||||
padding: const EdgeInsets.all(8),
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (_, idx) => _buildItem(idx, order[idx]),
|
||||
itemCount: order.length,
|
||||
proxyDecorator: _proxyDecorator,
|
||||
);
|
||||
});
|
||||
final serverState = ref.watch(serverNotifierProvider);
|
||||
final order = serverState.serverOrder;
|
||||
|
||||
if (order.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty));
|
||||
}
|
||||
return ReorderableListView.builder(
|
||||
footer: const SizedBox(height: 77),
|
||||
onReorder: (oldIndex, newIndex) {
|
||||
setState(() {
|
||||
final newOrder = List<String>.from(order);
|
||||
newOrder.move(oldIndex, newIndex);
|
||||
Stores.setting.serverOrder.put(newOrder);
|
||||
});
|
||||
},
|
||||
padding: const EdgeInsets.all(8),
|
||||
buildDefaultDragHandles: false,
|
||||
itemBuilder: (_, idx) => _buildItem(idx, order[idx]),
|
||||
itemCount: order.length,
|
||||
proxyDecorator: _proxyDecorator,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(int index, String id) {
|
||||
@@ -74,8 +77,10 @@ class _ServerOrderPageState extends State<ServerOrderPage> {
|
||||
}
|
||||
|
||||
Widget _buildCardTile(int index) {
|
||||
final id = ServerProvider.serverOrder.value[index];
|
||||
final spi = ServerProvider.pick(id: id)?.value.spi;
|
||||
final serverState = ref.watch(serverNotifierProvider);
|
||||
final order = serverState.serverOrder;
|
||||
final id = order[index];
|
||||
final spi = serverState.servers[id];
|
||||
if (spi == null) {
|
||||
return const SizedBox();
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
@@ -11,18 +12,18 @@ final class SnippetEditPageArgs {
|
||||
const SnippetEditPageArgs({this.snippet});
|
||||
}
|
||||
|
||||
class SnippetEditPage extends StatefulWidget {
|
||||
class SnippetEditPage extends ConsumerStatefulWidget {
|
||||
final SnippetEditPageArgs? args;
|
||||
|
||||
const SnippetEditPage({super.key, this.args});
|
||||
|
||||
@override
|
||||
State<SnippetEditPage> createState() => _SnippetEditPageState();
|
||||
ConsumerState<SnippetEditPage> createState() => _SnippetEditPageState();
|
||||
|
||||
static const route = AppRoute(page: SnippetEditPage.new, path: '/snippets/edit');
|
||||
}
|
||||
|
||||
class _SnippetEditPageState extends State<SnippetEditPage> with AfterLayoutMixin {
|
||||
class _SnippetEditPageState extends ConsumerState<SnippetEditPage> with AfterLayoutMixin {
|
||||
final _nameController = TextEditingController();
|
||||
final _scriptController = TextEditingController();
|
||||
final _noteController = TextEditingController();
|
||||
@@ -61,7 +62,7 @@ class _SnippetEditPageState extends State<SnippetEditPage> with AfterLayoutMixin
|
||||
child: Text(libL10n.askContinue('${libL10n.delete} ${l10n.snippet}(${snippet.name})')),
|
||||
actions: Btn.ok(
|
||||
onTap: () {
|
||||
SnippetProvider.del(snippet);
|
||||
ref.read(snippetNotifierProvider.notifier).del(snippet);
|
||||
context.pop();
|
||||
context.pop();
|
||||
},
|
||||
@@ -95,10 +96,11 @@ class _SnippetEditPageState extends State<SnippetEditPage> with AfterLayoutMixin
|
||||
autoRunOn: _autoRunOn.value.isEmpty ? null : _autoRunOn.value,
|
||||
);
|
||||
final oldSnippet = widget.args?.snippet;
|
||||
final notifier = ref.read(snippetNotifierProvider.notifier);
|
||||
if (oldSnippet != null) {
|
||||
SnippetProvider.update(oldSnippet, snippet);
|
||||
notifier.update(oldSnippet, snippet);
|
||||
} else {
|
||||
SnippetProvider.add(snippet);
|
||||
notifier.add(snippet);
|
||||
}
|
||||
context.pop();
|
||||
},
|
||||
@@ -126,7 +128,12 @@ class _SnippetEditPageState extends State<SnippetEditPage> with AfterLayoutMixin
|
||||
icon: Icons.note,
|
||||
suggestion: true,
|
||||
),
|
||||
TagTile(tags: _tags, allTags: SnippetProvider.tags.value).cardx,
|
||||
Consumer(
|
||||
builder: (_, ref, _) {
|
||||
final tags = ref.watch(snippetNotifierProvider.select((p) => p.tags));
|
||||
return TagTile(tags: _tags, allTags: tags).cardx;
|
||||
},
|
||||
),
|
||||
Input(
|
||||
controller: _scriptController,
|
||||
node: _scriptNode,
|
||||
@@ -150,7 +157,7 @@ class _SnippetEditPageState extends State<SnippetEditPage> with AfterLayoutMixin
|
||||
builder: (vals) {
|
||||
final subtitle = vals.isEmpty
|
||||
? null
|
||||
: vals.map((e) => ServerProvider.pick(id: e)?.value.spi.name ?? e).join(', ');
|
||||
: vals.map((e) => ref.read(serverNotifierProvider).servers[e]?.name ?? e).join(', ');
|
||||
return ListTile(
|
||||
leading: const Padding(
|
||||
padding: EdgeInsets.only(left: 5),
|
||||
@@ -162,11 +169,11 @@ class _SnippetEditPageState extends State<SnippetEditPage> with AfterLayoutMixin
|
||||
? null
|
||||
: Text(subtitle, maxLines: 1, style: UIs.textGrey, overflow: TextOverflow.ellipsis),
|
||||
onTap: () async {
|
||||
vals.removeWhere((e) => !ServerProvider.serverOrder.value.contains(e));
|
||||
vals.removeWhere((e) => !ref.read(serverNotifierProvider).serverOrder.contains(e));
|
||||
final serverIds = await context.showPickDialog(
|
||||
title: l10n.autoRun,
|
||||
items: ServerProvider.serverOrder.value,
|
||||
display: (e) => ServerProvider.pick(id: e)?.value.spi.name ?? e,
|
||||
items: ref.read(serverNotifierProvider).serverOrder,
|
||||
display: (e) => ref.read(serverNotifierProvider).servers[e]?.name ?? e,
|
||||
initial: vals,
|
||||
clearable: true,
|
||||
);
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/provider/snippet.dart';
|
||||
import 'package:server_box/view/page/snippet/edit.dart';
|
||||
|
||||
class SnippetListPage extends StatefulWidget {
|
||||
class SnippetListPage extends ConsumerStatefulWidget {
|
||||
const SnippetListPage({super.key});
|
||||
|
||||
@override
|
||||
State<SnippetListPage> createState() => _SnippetListPageState();
|
||||
ConsumerState<SnippetListPage> createState() => _SnippetListPageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: SnippetListPage.new, path: '/snippets');
|
||||
}
|
||||
|
||||
class _SnippetListPageState extends State<SnippetListPage> with AutomaticKeepAliveClientMixin {
|
||||
class _SnippetListPageState extends ConsumerState<SnippetListPage> with AutomaticKeepAliveClientMixin {
|
||||
final _tag = ''.vn;
|
||||
final _splitViewCtrl = SplitViewController();
|
||||
|
||||
@@ -35,12 +36,14 @@ class _SnippetListPageState extends State<SnippetListPage> with AutomaticKeepAli
|
||||
|
||||
Widget _buildBody() {
|
||||
// final isMobile = ResponsiveBreakpoints.of(context).isMobile;
|
||||
return SnippetProvider.snippets.listenVal((snippets) {
|
||||
return _tag.listenVal((tag) {
|
||||
final child = _buildScaffold(snippets, tag);
|
||||
// if (isMobile) {
|
||||
return child;
|
||||
// }
|
||||
final snippetState = ref.watch(snippetNotifierProvider);
|
||||
final snippets = snippetState.snippets;
|
||||
|
||||
return _tag.listenVal((tag) {
|
||||
final child = _buildScaffold(snippets, tag);
|
||||
// if (isMobile) {
|
||||
return child;
|
||||
// }
|
||||
|
||||
// return SplitView(
|
||||
// controller: _splitViewCtrl,
|
||||
@@ -49,14 +52,14 @@ class _SnippetListPageState extends State<SnippetListPage> with AutomaticKeepAli
|
||||
// initialRight: Center(child: Text(libL10n.empty)),
|
||||
// leftBuilder: (_, __) => child,
|
||||
// );
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildScaffold(List<Snippet> snippets, String tag) {
|
||||
final snippetState = ref.watch(snippetNotifierProvider);
|
||||
return Scaffold(
|
||||
appBar: TagSwitcher(
|
||||
tags: SnippetProvider.tags,
|
||||
tags: snippetState.tags.vn,
|
||||
onTagChanged: (tag) => _tag.value = tag,
|
||||
initTag: _tag.value,
|
||||
),
|
||||
|
||||
@@ -66,7 +66,8 @@ extension _Init on SSHPageState {
|
||||
// Mark status connected for notifications / live activities
|
||||
TermSessionManager.updateStatus(_sessionId, TermSessionStatus.connected);
|
||||
|
||||
for (final snippet in SnippetProvider.snippets.value) {
|
||||
final snippets = ref.read(snippetNotifierProvider.select((p) => p.snippets));
|
||||
for (final snippet in snippets) {
|
||||
if (snippet.autoRunOn?.contains(widget.args.spi.id) == true) {
|
||||
snippet.runInTerm(_terminal, widget.args.spi);
|
||||
}
|
||||
|
||||
@@ -7,9 +7,8 @@ import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import 'package:server_box/core/chan.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/utils/server.dart';
|
||||
@@ -17,6 +16,7 @@ import 'package:server_box/core/utils/ssh_auth.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/model/server/snippet.dart';
|
||||
import 'package:server_box/data/model/ssh/virtual_key.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/provider/snippet.dart';
|
||||
import 'package:server_box/data/provider/virtual_keyboard.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
@@ -52,23 +52,22 @@ final class SshPageArgs {
|
||||
});
|
||||
}
|
||||
|
||||
class SSHPage extends StatefulWidget {
|
||||
class SSHPage extends ConsumerStatefulWidget {
|
||||
final SshPageArgs args;
|
||||
|
||||
const SSHPage({super.key, required this.args});
|
||||
|
||||
@override
|
||||
State<SSHPage> createState() => SSHPageState();
|
||||
ConsumerState<SSHPage> createState() => SSHPageState();
|
||||
|
||||
static const route = AppRouteArg<void, SshPageArgs>(page: SSHPage.new, path: '/ssh/page');
|
||||
}
|
||||
|
||||
const _horizonPadding = 7.0;
|
||||
|
||||
class SSHPageState extends State<SSHPage>
|
||||
class SSHPageState extends ConsumerState<SSHPage>
|
||||
with AutomaticKeepAliveClientMixin, AfterLayoutMixin, TickerProviderStateMixin {
|
||||
final _keyboard = VirtKeyProvider();
|
||||
late final _terminal = Terminal(inputHandler: _keyboard);
|
||||
late final _terminal = Terminal();
|
||||
late final TerminalController _terminalController = TerminalController(vsync: this);
|
||||
final List<List<VirtKey>> _virtKeysList = [];
|
||||
late final _termKey = widget.args.terminalKey ?? GlobalKey<TerminalViewState>();
|
||||
@@ -81,7 +80,7 @@ class SSHPageState extends State<SSHPage>
|
||||
|
||||
bool _isDark = false;
|
||||
Timer? _virtKeyLongPressTimer;
|
||||
late SSHClient? _client = widget.args.spi.server?.value.client;
|
||||
SSHClient? _client;
|
||||
SSHSession? _session;
|
||||
Timer? _discontinuityTimer;
|
||||
|
||||
@@ -117,6 +116,10 @@ class SSHPageState extends State<SSHPage>
|
||||
_initStoredCfg();
|
||||
_initVirtKeys();
|
||||
_setupDiscontinuityTimer();
|
||||
|
||||
// Initialize client from provider
|
||||
final serverState = ref.read(individualServerNotifierProvider(widget.args.spi.id));
|
||||
_client = serverState.client;
|
||||
|
||||
if (++_sshConnCount == 1) {
|
||||
WakelockPlus.enable();
|
||||
@@ -262,19 +265,22 @@ class SSHPageState extends State<SSHPage>
|
||||
child: Container(
|
||||
color: _terminalTheme.background,
|
||||
height: _virtKeysHeight + _media.padding.bottom,
|
||||
child: ChangeNotifierProvider(
|
||||
create: (_) => _keyboard,
|
||||
builder: (_, _) => Consumer<VirtKeyProvider>(
|
||||
builder: (_, _, _) {
|
||||
return _buildVirtualKey();
|
||||
},
|
||||
),
|
||||
child: Consumer(
|
||||
builder: (context, ref, child) {
|
||||
final virtKeyState = ref.watch(virtKeyboardProvider);
|
||||
final virtKeyNotifier = ref.read(virtKeyboardProvider.notifier);
|
||||
|
||||
// Set the terminal input handler
|
||||
_terminal.inputHandler = virtKeyNotifier;
|
||||
|
||||
return _buildVirtualKey(virtKeyState, virtKeyNotifier);
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVirtualKey() {
|
||||
Widget _buildVirtualKey(VirtKeyState virtKeyState, VirtKeyboard virtKeyNotifier) {
|
||||
final count = _horizonVirtKeys ? _virtKeysList.length : _virtKeysList.firstOrNull?.length ?? 0;
|
||||
if (count == 0) return UIs.placeholder;
|
||||
return LayoutBuilder(
|
||||
@@ -286,30 +292,30 @@ class SSHPageState extends State<SSHPage>
|
||||
child: Row(
|
||||
children: _virtKeysList
|
||||
.expand((e) => e)
|
||||
.map((e) => _buildVirtKeyItem(e, virtKeyWidth))
|
||||
.map((e) => _buildVirtKeyItem(e, virtKeyWidth, virtKeyState, virtKeyNotifier))
|
||||
.toList(),
|
||||
),
|
||||
);
|
||||
}
|
||||
final rows = _virtKeysList
|
||||
.map((e) => Row(children: e.map((e) => _buildVirtKeyItem(e, virtKeyWidth)).toList()))
|
||||
.map((e) => Row(children: e.map((e) => _buildVirtKeyItem(e, virtKeyWidth, virtKeyState, virtKeyNotifier)).toList()))
|
||||
.toList();
|
||||
return Column(mainAxisSize: MainAxisSize.min, children: rows);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildVirtKeyItem(VirtKey item, double virtKeyWidth) {
|
||||
Widget _buildVirtKeyItem(VirtKey item, double virtKeyWidth, VirtKeyState virtKeyState, VirtKeyboard virtKeyNotifier) {
|
||||
var selected = false;
|
||||
switch (item.key) {
|
||||
case TerminalKey.control:
|
||||
selected = _keyboard.ctrl;
|
||||
selected = virtKeyState.ctrl;
|
||||
break;
|
||||
case TerminalKey.alt:
|
||||
selected = _keyboard.alt;
|
||||
selected = virtKeyState.alt;
|
||||
break;
|
||||
case TerminalKey.shift:
|
||||
selected = _keyboard.shift;
|
||||
selected = virtKeyState.shift;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -326,12 +332,12 @@ class SSHPageState extends State<SSHPage>
|
||||
);
|
||||
|
||||
return InkWell(
|
||||
onTap: () => _doVirtualKey(item),
|
||||
onTap: () => _doVirtualKey(item, virtKeyNotifier),
|
||||
onTapDown: (details) {
|
||||
if (item.canLongPress) {
|
||||
_virtKeyLongPressTimer = Timer.periodic(
|
||||
const Duration(milliseconds: 137),
|
||||
(_) => _doVirtualKey(item),
|
||||
(_) => _doVirtualKey(item, virtKeyNotifier),
|
||||
);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
part of 'page.dart';
|
||||
|
||||
extension _VirtKey on SSHPageState {
|
||||
void _doVirtualKey(VirtKey item) {
|
||||
void _doVirtualKey(VirtKey item, VirtKeyboard virtKeyNotifier) {
|
||||
if (item.func != null) {
|
||||
HapticFeedback.mediumImpact();
|
||||
_doVirtualKeyFunc(item.func!);
|
||||
@@ -9,7 +9,7 @@ extension _VirtKey on SSHPageState {
|
||||
}
|
||||
if (item.key != null) {
|
||||
HapticFeedback.mediumImpact();
|
||||
_doVirtualKeyInput(item.key!);
|
||||
_doVirtualKeyInput(item.key!, virtKeyNotifier);
|
||||
}
|
||||
final inputRaw = item.inputRaw;
|
||||
if (inputRaw != null) {
|
||||
@@ -18,16 +18,16 @@ extension _VirtKey on SSHPageState {
|
||||
}
|
||||
}
|
||||
|
||||
void _doVirtualKeyInput(TerminalKey key) {
|
||||
void _doVirtualKeyInput(TerminalKey key, VirtKeyboard virtKeyNotifier) {
|
||||
switch (key) {
|
||||
case TerminalKey.control:
|
||||
_keyboard.ctrl = !_keyboard.ctrl;
|
||||
virtKeyNotifier.setCtrl(!virtKeyNotifier.ctrl);
|
||||
break;
|
||||
case TerminalKey.alt:
|
||||
_keyboard.alt = !_keyboard.alt;
|
||||
virtKeyNotifier.setAlt(!virtKeyNotifier.alt);
|
||||
break;
|
||||
case TerminalKey.shift:
|
||||
_keyboard.shift = !_keyboard.shift;
|
||||
virtKeyNotifier.setShift(!virtKeyNotifier.shift);
|
||||
break;
|
||||
default:
|
||||
_terminal.keyInput(key);
|
||||
@@ -52,14 +52,15 @@ extension _VirtKey on SSHPageState {
|
||||
}
|
||||
break;
|
||||
case VirtualKeyFunc.snippet:
|
||||
final snippetState = ref.read(snippetNotifierProvider);
|
||||
final snippets = await context.showPickWithTagDialog<Snippet>(
|
||||
title: l10n.snippet,
|
||||
tags: SnippetProvider.tags,
|
||||
tags: snippetState.tags.vn,
|
||||
itemsBuilder: (e) {
|
||||
if (e == TagSwitcher.kDefaultTag) {
|
||||
return SnippetProvider.snippets.value;
|
||||
return snippetState.snippets;
|
||||
}
|
||||
return SnippetProvider.snippets.value
|
||||
return snippetState.snippets
|
||||
.where((element) => element.tags?.contains(e) ?? false)
|
||||
.toList();
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ import 'dart:math';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
@@ -10,18 +11,18 @@ import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/view/page/server/edit.dart';
|
||||
import 'package:server_box/view/page/ssh/page/page.dart';
|
||||
|
||||
class SSHTabPage extends StatefulWidget {
|
||||
class SSHTabPage extends ConsumerStatefulWidget {
|
||||
const SSHTabPage({super.key});
|
||||
|
||||
@override
|
||||
State<SSHTabPage> createState() => _SSHTabPageState();
|
||||
ConsumerState<SSHTabPage> createState() => _SSHTabPageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: SSHTabPage.new, path: '/ssh');
|
||||
}
|
||||
|
||||
typedef _TabMap = Map<String, ({Widget page, FocusNode? focus})>;
|
||||
|
||||
class _SSHTabPageState extends State<SSHTabPage>
|
||||
class _SSHTabPageState extends ConsumerState<SSHTabPage>
|
||||
with TickerProviderStateMixin, AutomaticKeepAliveClientMixin {
|
||||
late final _TabMap _tabMap = {libL10n.add: (page: _AddPage(onTapInitCard: _onTapInitCard), focus: null)};
|
||||
final _pageCtrl = PageController();
|
||||
@@ -236,7 +237,7 @@ final class _TabBar extends StatelessWidget implements PreferredSizeWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class _AddPage extends StatelessWidget {
|
||||
class _AddPage extends ConsumerWidget {
|
||||
const _AddPage({required this.onTapInitCard});
|
||||
|
||||
final void Function(Spi spi) onTapInitCard;
|
||||
@@ -244,11 +245,12 @@ class _AddPage extends StatelessWidget {
|
||||
Widget get _placeholder => const Expanded(child: UIs.placeholder);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
const viewPadding = 7.0;
|
||||
final viewWidth = context.mediaQuery.size.width - 2 * viewPadding;
|
||||
final viewWidth = context.windowSize.width - 2 * viewPadding;
|
||||
|
||||
final itemCount = ServerProvider.servers.length;
|
||||
final serverState = ref.watch(serverNotifierProvider);
|
||||
final itemCount = serverState.servers.length;
|
||||
const itemPadding = 1.0;
|
||||
const itemWidth = 150.0;
|
||||
const itemHeight = 50.0;
|
||||
@@ -257,53 +259,53 @@ class _AddPage extends StatelessWidget {
|
||||
final crossCount = max(viewWidth ~/ (visualCrossCount * itemPadding + itemWidth), 1);
|
||||
final mainCount = itemCount ~/ crossCount + 1;
|
||||
|
||||
return ServerProvider.serverOrder.listenVal((order) {
|
||||
if (order.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty, textAlign: TextAlign.center));
|
||||
}
|
||||
final order = serverState.serverOrder;
|
||||
|
||||
if (order.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty, textAlign: TextAlign.center));
|
||||
}
|
||||
|
||||
// Custom grid
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(viewPadding),
|
||||
children: List.generate(
|
||||
mainCount,
|
||||
(rowIndex) => Row(
|
||||
children: List.generate(crossCount, (columnIndex) {
|
||||
final idx = rowIndex * crossCount + columnIndex;
|
||||
final id = order.elementAtOrNull(idx);
|
||||
final spi = ServerProvider.pick(id: id)?.value.spi;
|
||||
if (spi == null) return _placeholder;
|
||||
// Custom grid
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(viewPadding),
|
||||
children: List.generate(
|
||||
mainCount,
|
||||
(rowIndex) => Row(
|
||||
children: List.generate(crossCount, (columnIndex) {
|
||||
final idx = rowIndex * crossCount + columnIndex;
|
||||
final id = order.elementAtOrNull(idx);
|
||||
final spi = serverState.servers[id];
|
||||
if (spi == null) return _placeholder;
|
||||
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(itemPadding),
|
||||
child: InkWell(
|
||||
onTap: () => onTapInitCard(spi),
|
||||
child: Container(
|
||||
height: itemHeight,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.only(left: 17, right: 7),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
spi.name,
|
||||
style: UIs.text18,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
return Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(itemPadding),
|
||||
child: InkWell(
|
||||
onTap: () => onTapInitCard(spi),
|
||||
child: Container(
|
||||
height: itemHeight,
|
||||
alignment: Alignment.centerLeft,
|
||||
padding: const EdgeInsets.only(left: 17, right: 7),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(
|
||||
spi.name,
|
||||
style: UIs.text18,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
),
|
||||
const Icon(Icons.chevron_right),
|
||||
],
|
||||
),
|
||||
).cardx,
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
).cardx,
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
);
|
||||
});
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/app/path_with_prefix.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
@@ -19,7 +20,7 @@ final class LocalFilePageArgs {
|
||||
const LocalFilePageArgs({this.isPickFile, this.initDir});
|
||||
}
|
||||
|
||||
class LocalFilePage extends StatefulWidget {
|
||||
class LocalFilePage extends ConsumerStatefulWidget {
|
||||
final LocalFilePageArgs? args;
|
||||
|
||||
const LocalFilePage({super.key, this.args});
|
||||
@@ -27,10 +28,10 @@ class LocalFilePage extends StatefulWidget {
|
||||
static const route = AppRoute<String, LocalFilePageArgs>(page: LocalFilePage.new, path: '/files/local');
|
||||
|
||||
@override
|
||||
State<LocalFilePage> createState() => _LocalFilePageState();
|
||||
ConsumerState<LocalFilePage> createState() => _LocalFilePageState();
|
||||
}
|
||||
|
||||
class _LocalFilePageState extends State<LocalFilePage> with AutomaticKeepAliveClientMixin {
|
||||
class _LocalFilePageState extends ConsumerState<LocalFilePage> with AutomaticKeepAliveClientMixin {
|
||||
late final _path = LocalPath(widget.args?.initDir ?? Paths.file);
|
||||
final _sortType = _SortType.name.vn;
|
||||
bool get isPickFile => widget.args?.isPickFile ?? false;
|
||||
@@ -358,10 +359,7 @@ extension _OnTapFile on _LocalFilePageState {
|
||||
|
||||
final spi = await context.showPickSingleDialog<Spi>(
|
||||
title: libL10n.select,
|
||||
items: ServerProvider.serverOrder.value
|
||||
.map((e) => ServerProvider.pick(id: e)?.value.spi)
|
||||
.whereType<Spi>()
|
||||
.toList(),
|
||||
items: ref.read(serverNotifierProvider).servers.values.toList(),
|
||||
display: (e) => e.name,
|
||||
);
|
||||
if (spi == null) return;
|
||||
@@ -372,7 +370,7 @@ extension _OnTapFile on _LocalFilePageState {
|
||||
return;
|
||||
}
|
||||
|
||||
SftpProvider.add(SftpReq(spi, '$remotePath/$fileName', file.absolute.path, SftpReqType.upload));
|
||||
ref.read(sftpNotifierProvider.notifier).add(SftpReq(spi, '$remotePath/$fileName', file.absolute.path, SftpReqType.upload));
|
||||
context.showSnackBar(l10n.added2List);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import 'dart:io';
|
||||
import 'package:dartssh2/dartssh2.dart';
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:icons_plus/icons_plus.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/extension/sftpfile.dart';
|
||||
@@ -11,6 +12,7 @@ import 'package:server_box/core/utils/comparator.dart';
|
||||
import 'package:server_box/data/model/server/server_private_info.dart';
|
||||
import 'package:server_box/data/model/sftp/browser_status.dart';
|
||||
import 'package:server_box/data/model/sftp/worker.dart';
|
||||
import 'package:server_box/data/provider/server.dart';
|
||||
import 'package:server_box/data/provider/sftp.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
import 'package:server_box/data/res/store.dart';
|
||||
@@ -29,21 +31,29 @@ final class SftpPageArgs {
|
||||
const SftpPageArgs({required this.spi, this.isSelect = false, this.initPath});
|
||||
}
|
||||
|
||||
class SftpPage extends StatefulWidget {
|
||||
class SftpPage extends ConsumerStatefulWidget {
|
||||
final SftpPageArgs args;
|
||||
|
||||
const SftpPage({super.key, required this.args});
|
||||
|
||||
@override
|
||||
State<SftpPage> createState() => _SftpPageState();
|
||||
ConsumerState<SftpPage> createState() => _SftpPageState();
|
||||
|
||||
static const route = AppRouteArg<String, SftpPageArgs>(page: SftpPage.new, path: '/sftp');
|
||||
}
|
||||
|
||||
class _SftpPageState extends State<SftpPage> with AfterLayoutMixin {
|
||||
late final _status = SftpBrowserStatus(_client);
|
||||
late final _client = widget.args.spi.server!.value.client!;
|
||||
class _SftpPageState extends ConsumerState<SftpPage> with AfterLayoutMixin {
|
||||
late final SftpBrowserStatus _status;
|
||||
late final SSHClient _client;
|
||||
final _sortOption = _SortOption().vn;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final serverState = ref.read(individualServerNotifierProvider(widget.args.spi.id));
|
||||
_client = serverState.client!;
|
||||
_status = SftpBrowserStatus(_client);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
@@ -280,7 +290,7 @@ extension _Actions on _SftpPageState {
|
||||
final localPath = _getLocalPath(remotePath);
|
||||
final completer = Completer();
|
||||
final req = SftpReq(widget.args.spi, remotePath, localPath, SftpReqType.download);
|
||||
SftpProvider.add(req, completer: completer);
|
||||
ref.read(sftpNotifierProvider.notifier).add(req, completer: completer);
|
||||
final (suc, err) = await context.showLoadingDialog(fn: () => completer.future);
|
||||
if (suc == null || err != null) return;
|
||||
|
||||
@@ -289,7 +299,9 @@ extension _Actions on _SftpPageState {
|
||||
args: EditorPageArgs(
|
||||
path: localPath,
|
||||
onSave: (_) {
|
||||
SftpProvider.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
|
||||
ref
|
||||
.read(sftpNotifierProvider.notifier)
|
||||
.add(SftpReq(req.spi, remotePath, localPath, SftpReqType.upload));
|
||||
context.showSnackBar(l10n.added2List);
|
||||
},
|
||||
closeAfterSave: SettingStore.instance.closeAfterSave.fetch(),
|
||||
@@ -310,9 +322,9 @@ extension _Actions on _SftpPageState {
|
||||
context.pop();
|
||||
final remotePath = _getRemotePath(name);
|
||||
|
||||
SftpProvider.add(
|
||||
SftpReq(widget.args.spi, remotePath, _getLocalPath(remotePath), SftpReqType.download),
|
||||
);
|
||||
ref
|
||||
.read(sftpNotifierProvider.notifier)
|
||||
.add(SftpReq(widget.args.spi, remotePath, _getLocalPath(remotePath), SftpReqType.download));
|
||||
|
||||
context.pop();
|
||||
},
|
||||
@@ -640,7 +652,9 @@ extension _Actions on _SftpPageState {
|
||||
final fileName = path.split(Platform.pathSeparator).lastOrNull;
|
||||
final remotePath = '$remoteDir/$fileName';
|
||||
Loggers.app.info('SFTP upload local: $path, remote: $remotePath');
|
||||
SftpProvider.add(SftpReq(widget.args.spi, remotePath, path, SftpReqType.upload));
|
||||
ref
|
||||
.read(sftpNotifierProvider.notifier)
|
||||
.add(SftpReq(widget.args.spi, remotePath, path, SftpReqType.upload));
|
||||
},
|
||||
icon: const Icon(Icons.upload_file),
|
||||
);
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/sftp/worker.dart';
|
||||
import 'package:server_box/data/provider/sftp.dart';
|
||||
import 'package:server_box/view/page/storage/local.dart';
|
||||
|
||||
class SftpMissionPage extends StatefulWidget {
|
||||
class SftpMissionPage extends ConsumerStatefulWidget {
|
||||
const SftpMissionPage({super.key});
|
||||
|
||||
@override
|
||||
State<SftpMissionPage> createState() => _SftpMissionPageState();
|
||||
ConsumerState<SftpMissionPage> createState() => _SftpMissionPageState();
|
||||
|
||||
static const route = AppRouteNoArg(page: SftpMissionPage.new, path: '/sftp/mission');
|
||||
}
|
||||
|
||||
class _SftpMissionPageState extends State<SftpMissionPage> {
|
||||
class _SftpMissionPageState extends ConsumerState<SftpMissionPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@@ -24,18 +25,17 @@ class _SftpMissionPageState extends State<SftpMissionPage> {
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
return SftpProvider.status.listenVal((status) {
|
||||
if (status.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty));
|
||||
}
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(11),
|
||||
itemCount: status.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildItem(status[index]);
|
||||
},
|
||||
);
|
||||
});
|
||||
final status = ref.watch(sftpNotifierProvider.select((pro) => pro.requests));
|
||||
if (status.isEmpty) {
|
||||
return Center(child: Text(libL10n.empty));
|
||||
}
|
||||
return ListView.builder(
|
||||
padding: const EdgeInsets.all(11),
|
||||
itemCount: status.length,
|
||||
itemBuilder: (context, index) {
|
||||
return _buildItem(status[index]);
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(SftpReqStatus status) {
|
||||
@@ -143,7 +143,7 @@ class _SftpMissionPageState extends State<SftpMissionPage> {
|
||||
child: Text(libL10n.askContinue('${libL10n.delete} ${l10n.mission}($name)')),
|
||||
actions: Btn.ok(
|
||||
onTap: () {
|
||||
SftpProvider.cancel(id);
|
||||
ref.read(sftpNotifierProvider.notifier).cancel(id);
|
||||
context.pop();
|
||||
},
|
||||
).toList,
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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';
|
||||
import 'package:server_box/view/page/ssh/page/page.dart';
|
||||
|
||||
final class SystemdPage extends StatefulWidget {
|
||||
final class SystemdPage extends ConsumerStatefulWidget {
|
||||
final SpiRequiredArgs args;
|
||||
|
||||
const SystemdPage({super.key, required this.args});
|
||||
@@ -14,44 +15,39 @@ final class SystemdPage extends StatefulWidget {
|
||||
static const route = AppRouteArg<void, SpiRequiredArgs>(page: SystemdPage.new, path: '/systemd');
|
||||
|
||||
@override
|
||||
State<SystemdPage> createState() => _SystemdPageState();
|
||||
ConsumerState<SystemdPage> createState() => _SystemdPageState();
|
||||
}
|
||||
|
||||
final class _SystemdPageState extends State<SystemdPage> {
|
||||
late final _pro = SystemdProvider.init(widget.args.spi);
|
||||
final class _SystemdPageState extends ConsumerState<SystemdPage> {
|
||||
late final _pro = systemdNotifierProvider(widget.args.spi);
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
_pro.dispose();
|
||||
}
|
||||
late final _notifier = ref.read(_pro.notifier);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: CustomAppBar(
|
||||
title: const Text('Systemd'),
|
||||
actions: isDesktop ? [Btn.icon(icon: const Icon(Icons.refresh), onTap: _pro.getUnits)] : null,
|
||||
actions: isDesktop ? [Btn.icon(icon: const Icon(Icons.refresh), onTap: _notifier.getUnits)] : null,
|
||||
),
|
||||
body: RefreshIndicator(onRefresh: _pro.getUnits, child: _buildBody()),
|
||||
body: RefreshIndicator(onRefresh: _notifier.getUnits, child: _buildBody()),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBody() {
|
||||
final isBusy = ref.watch(_pro.select((pro) => pro.isBusy));
|
||||
return CustomScrollView(
|
||||
slivers: <Widget>[
|
||||
SliverToBoxAdapter(
|
||||
child: Column(
|
||||
children: [
|
||||
_buildScopeFilterChips(),
|
||||
_pro.isBusy.listenVal(
|
||||
(isBusy) => AnimatedContainer(
|
||||
duration: Durations.medium1,
|
||||
curve: Curves.fastEaseInToSlowEaseOut,
|
||||
height: isBusy ? SizedLoading.medium.size : 0,
|
||||
width: isBusy ? SizedLoading.medium.size : 0,
|
||||
child: isBusy ? SizedLoading.medium : const SizedBox.shrink(),
|
||||
),
|
||||
AnimatedContainer(
|
||||
duration: Durations.medium1,
|
||||
curve: Curves.fastEaseInToSlowEaseOut,
|
||||
height: isBusy ? SizedLoading.medium.size : 0,
|
||||
width: isBusy ? SizedLoading.medium.size : 0,
|
||||
child: isBusy ? SizedLoading.medium : const SizedBox.shrink(),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -62,43 +58,40 @@ final class _SystemdPageState extends State<SystemdPage> {
|
||||
}
|
||||
|
||||
Widget _buildScopeFilterChips() {
|
||||
return _pro.scopeFilter.listenVal((currentFilter) {
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
children: SystemdScopeFilter.values.map((filter) {
|
||||
final isSelected = filter == currentFilter;
|
||||
return FilterChip(
|
||||
selected: isSelected,
|
||||
label: Text(filter.displayName),
|
||||
onSelected: (_) => _pro.scopeFilter.value = filter,
|
||||
);
|
||||
}).toList(),
|
||||
).paddingSymmetric(horizontal: 13, vertical: 8);
|
||||
});
|
||||
final currentFilter = ref.watch(_pro.select((p) => p.scopeFilter));
|
||||
return Wrap(
|
||||
spacing: 8,
|
||||
children: SystemdScopeFilter.values.map((filter) {
|
||||
final isSelected = filter == currentFilter;
|
||||
return FilterChip(
|
||||
selected: isSelected,
|
||||
label: Text(filter.displayName),
|
||||
onSelected: (_) => _notifier.setScopeFilter(filter),
|
||||
);
|
||||
}).toList(),
|
||||
).paddingSymmetric(horizontal: 13, vertical: 8);
|
||||
}
|
||||
|
||||
Widget _buildUnitList() {
|
||||
return _pro.units.listenVal((allUnits) {
|
||||
return _pro.scopeFilter.listenVal((filter) {
|
||||
final filteredUnits = _pro.filteredUnits;
|
||||
if (filteredUnits.isEmpty) {
|
||||
return SliverToBoxAdapter(child: CenterGreyTitle(libL10n.empty).paddingSymmetric(horizontal: 13));
|
||||
}
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final unit = filteredUnits[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: filteredUnits.length),
|
||||
);
|
||||
});
|
||||
});
|
||||
ref.watch(_pro.select((p) => p.units));
|
||||
ref.watch(_pro.select((p) => p.scopeFilter));
|
||||
final filteredUnits = _notifier.filteredUnits;
|
||||
if (filteredUnits.isEmpty) {
|
||||
return SliverToBoxAdapter(child: CenterGreyTitle(libL10n.empty).paddingSymmetric(horizontal: 13));
|
||||
}
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate((context, index) {
|
||||
final unit = filteredUnits[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: filteredUnits.length),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildUnitFuncs(SystemdUnit unit) {
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:io';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/core/route.dart';
|
||||
import 'package:server_box/core/utils/server.dart';
|
||||
@@ -19,17 +20,17 @@ import 'package:server_box/view/page/ssh/page/page.dart';
|
||||
import 'package:server_box/view/page/storage/sftp.dart';
|
||||
import 'package:server_box/view/page/systemd.dart';
|
||||
|
||||
class ServerFuncBtnsTopRight extends StatelessWidget {
|
||||
class ServerFuncBtnsTopRight extends ConsumerWidget {
|
||||
final Spi spi;
|
||||
|
||||
const ServerFuncBtnsTopRight({super.key, required this.spi});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
return PopupMenu<ServerFuncBtn>(
|
||||
items: ServerFuncBtn.values.map((e) => PopMenu.build(e, e.icon, e.toStr)).toList(),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
onSelected: (val) => _onTapMoreBtns(val, spi, context),
|
||||
onSelected: (val) => _onTapMoreBtns(val, spi, context, ref),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -52,18 +53,18 @@ class ServerFuncBtns extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 13),
|
||||
itemBuilder: (context, index) {
|
||||
final value = btns[index];
|
||||
final item = _buildItem(context, value);
|
||||
final item = Consumer(builder: (_, ref, _) => _buildItem(context, value, ref));
|
||||
return item.paddingSymmetric(horizontal: 7);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildItem(BuildContext context, ServerFuncBtn e) {
|
||||
Widget _buildItem(BuildContext context, ServerFuncBtn e, WidgetRef ref) {
|
||||
final move = Stores.setting.moveServerFuncs.fetch();
|
||||
if (move) {
|
||||
return IconButton(
|
||||
onPressed: () => _onTapMoreBtns(e, spi, context),
|
||||
onPressed: () => _onTapMoreBtns(e, spi, context, ref),
|
||||
padding: EdgeInsets.zero,
|
||||
tooltip: e.toStr,
|
||||
icon: Icon(e.icon, size: 15),
|
||||
@@ -76,7 +77,7 @@ class ServerFuncBtns extends StatelessWidget {
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => _onTapMoreBtns(e, spi, context),
|
||||
onPressed: () => _onTapMoreBtns(e, spi, context, ref),
|
||||
padding: EdgeInsets.zero,
|
||||
icon: Icon(e.icon, size: 17),
|
||||
),
|
||||
@@ -101,14 +102,14 @@ class ServerFuncBtns extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context) async {
|
||||
void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context, WidgetRef ref) async {
|
||||
// final isMobile = ResponsiveBreakpoints.of(context).isMobile;
|
||||
switch (value) {
|
||||
// case ServerFuncBtn.pkg:
|
||||
// _onPkg(context, spi);
|
||||
// break;
|
||||
case ServerFuncBtn.sftp:
|
||||
if (!_checkClient(context, spi.id)) return;
|
||||
if (!_checkClient(context, spi.id, ref)) return;
|
||||
final args = SftpPageArgs(spi: spi);
|
||||
// if (isMobile) {
|
||||
SftpPage.route.go(context, args);
|
||||
@@ -120,18 +121,19 @@ void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context) async {
|
||||
|
||||
break;
|
||||
case ServerFuncBtn.snippet:
|
||||
if (SnippetProvider.snippets.value.isEmpty) {
|
||||
final snippetState = ref.read(snippetNotifierProvider);
|
||||
if (snippetState.snippets.isEmpty) {
|
||||
context.showSnackBar(libL10n.empty);
|
||||
return;
|
||||
}
|
||||
final snippets = await context.showPickWithTagDialog<Snippet>(
|
||||
title: l10n.snippet,
|
||||
tags: SnippetProvider.tags,
|
||||
tags: snippetState.tags.vn,
|
||||
itemsBuilder: (e) {
|
||||
if (e == TagSwitcher.kDefaultTag) {
|
||||
return SnippetProvider.snippets.value;
|
||||
return snippetState.snippets;
|
||||
}
|
||||
return SnippetProvider.snippets.value
|
||||
return snippetState.snippets
|
||||
.where((element) => element.tags?.contains(e) ?? false)
|
||||
.toList();
|
||||
},
|
||||
@@ -147,7 +149,7 @@ void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context) async {
|
||||
actions: [CountDownBtn(onTap: () => context.pop(true), text: l10n.run, afterColor: Colors.red)],
|
||||
);
|
||||
if (sure != true) return;
|
||||
if (!_checkClient(context, spi.id)) return;
|
||||
if (!_checkClient(context, spi.id, ref)) return;
|
||||
final args = SshPageArgs(spi: spi, initSnippet: snippet);
|
||||
// if (isMobile) {
|
||||
SSHPage.route.go(context, args);
|
||||
@@ -158,7 +160,7 @@ void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context) async {
|
||||
// }
|
||||
break;
|
||||
case ServerFuncBtn.container:
|
||||
if (!_checkClient(context, spi.id)) return;
|
||||
if (!_checkClient(context, spi.id, ref)) return;
|
||||
final args = SpiRequiredArgs(spi);
|
||||
// if (isMobile) {
|
||||
ContainerPage.route.go(context, args);
|
||||
@@ -169,7 +171,7 @@ void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context) async {
|
||||
// }
|
||||
break;
|
||||
case ServerFuncBtn.process:
|
||||
if (!_checkClient(context, spi.id)) return;
|
||||
if (!_checkClient(context, spi.id, ref)) return;
|
||||
final args = SpiRequiredArgs(spi);
|
||||
// if (isMobile) {
|
||||
ProcessPage.route.go(context, args);
|
||||
@@ -183,7 +185,7 @@ void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context) async {
|
||||
_gotoSSH(spi, context);
|
||||
break;
|
||||
case ServerFuncBtn.iperf:
|
||||
if (!_checkClient(context, spi.id)) return;
|
||||
if (!_checkClient(context, spi.id, ref)) return;
|
||||
final args = SpiRequiredArgs(spi);
|
||||
// if (isMobile) {
|
||||
IPerfPage.route.go(context, args);
|
||||
@@ -194,7 +196,7 @@ void _onTapMoreBtns(ServerFuncBtn value, Spi spi, BuildContext context) async {
|
||||
// }
|
||||
break;
|
||||
case ServerFuncBtn.systemd:
|
||||
if (!_checkClient(context, spi.id)) return;
|
||||
if (!_checkClient(context, spi.id, ref)) return;
|
||||
final args = SpiRequiredArgs(spi);
|
||||
// if (isMobile) {
|
||||
SystemdPage.route.go(context, args);
|
||||
@@ -270,9 +272,9 @@ void _gotoSSH(Spi spi, BuildContext context) async {
|
||||
}
|
||||
}
|
||||
|
||||
bool _checkClient(BuildContext context, String id) {
|
||||
final server = ServerProvider.pick(id: id)?.value;
|
||||
if (server == null || server.client == null) {
|
||||
bool _checkClient(BuildContext context, String id, WidgetRef ref) {
|
||||
final serverState = ref.read(individualServerNotifierProvider(id));
|
||||
if (serverState.client == null) {
|
||||
context.showSnackBar(l10n.waitConnection);
|
||||
return false;
|
||||
}
|
||||
|
||||
72
pubspec.lock
72
pubspec.lock
@@ -117,18 +117,18 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build
|
||||
sha256: "6439a9c71a4e6eca8d9490c1b380a25b02675aa688137dfbe66d2062884a23ac"
|
||||
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "2.5.4"
|
||||
build_config:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_config
|
||||
sha256: "4f64382b97504dc2fcdf487d5aae33418e08b4703fc21249e4db6d804a4d0187"
|
||||
sha256: "4ae2de3e1e67ea270081eaee972e1bd8f027d459f249e0f1186730784c2e7e33"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.0"
|
||||
version: "1.1.2"
|
||||
build_daemon:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -141,26 +141,26 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_resolvers
|
||||
sha256: "2b21a125d66a86b9511cc3fb6c668c42e9a1185083922bf60e46d483a81a9712"
|
||||
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
version: "2.5.4"
|
||||
build_runner:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: build_runner
|
||||
sha256: fd3c09f4bbff7fa6e8d8ef688a0b2e8a6384e6483a25af0dac75fef362bcfe6f
|
||||
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.7.0"
|
||||
version: "2.5.4"
|
||||
build_runner_core:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: build_runner_core
|
||||
sha256: ab27e46c8aa233e610cf6084ee6d8a22c6f873a0a9929241d8855b7a72978ae7
|
||||
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.3.0"
|
||||
version: "9.1.2"
|
||||
built_collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -343,10 +343,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: custom_lint_core
|
||||
sha256: cc4684d22ca05bf0a4a51127e19a8aea576b42079ed2bc9e956f11aaebe35dd1
|
||||
sha256: "31110af3dde9d29fb10828ca33f1dce24d2798477b167675543ce3d208dee8be"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.8.0"
|
||||
version: "0.7.5"
|
||||
custom_lint_visitor:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -497,8 +497,8 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "."
|
||||
ref: "v1.0.338"
|
||||
resolved-ref: d681131c436fa346bfb4675bca5c37cd38b1fdc2
|
||||
ref: "v1.0.343"
|
||||
resolved-ref: a65b7447ac2cc5c25e5a96dc559b1b67a40b2c82
|
||||
url: "https://github.com/lppcg/fl_lib"
|
||||
source: git
|
||||
version: "0.0.1"
|
||||
@@ -588,10 +588,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_riverpod
|
||||
sha256: "56c3cc75d04c34fc824ce1d52ec9076c431e3c47ed55fd8cbf9756ca6d50479e"
|
||||
sha256: "9532ee6db4a943a1ed8383072a2e3eeda041db5657cdf6d2acecf3c21ecbe7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "2.6.1"
|
||||
flutter_secure_storage:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -662,10 +662,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: freezed
|
||||
sha256: da32f8ba8cfcd4ec71d9decc8cbf28bd2c31b5283d9887eb51eb4a0659d8110c
|
||||
sha256: "2d399f823b8849663744d2a9ddcce01c49268fb4170d0442a655bf6a2f47be22"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
version: "3.1.0"
|
||||
freezed_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -735,10 +735,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: hive_ce_generator
|
||||
sha256: a169feeff2da9cc2c417ce5ae9bcebf7c8a95d7a700492b276909016ad70a786
|
||||
sha256: "609678c10ebee7503505a0007050af40a0a4f498b1fb7def3220df341e573a89"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.3"
|
||||
version: "1.9.2"
|
||||
html:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -863,10 +863,10 @@ packages:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: json_serializable
|
||||
sha256: ce2cf974ccdee13be2a510832d7fba0b94b364e0b0395dee42abaa51b855be27
|
||||
sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.10.0"
|
||||
version: "6.9.5"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -995,14 +995,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.0"
|
||||
mockito:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mockito
|
||||
sha256: "2314cbe9165bcd16106513df9cf3c3224713087f09723b128928dc11a4379f99"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.5.0"
|
||||
multi_split_view:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1272,34 +1264,34 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod
|
||||
sha256: "82507cfb140c044f12e929c054dcdfc478359f473bcd2976af26908318e91b8e"
|
||||
sha256: "59062512288d3056b2321804332a13ffdd1bf16df70dcc8e506e411280a72959"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "2.6.1"
|
||||
riverpod_analyzer_utils:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: riverpod_analyzer_utils
|
||||
sha256: ce9dfa8dccc5029535a09d1582681c894c8853613aaca5869d372348cf432114
|
||||
sha256: "03a17170088c63aab6c54c44456f5ab78876a1ddb6032ffde1662ddab4959611"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0-dev.4"
|
||||
version: "0.5.10"
|
||||
riverpod_annotation:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: riverpod_annotation
|
||||
sha256: d4449ce911fe1e211a2f6fbc110c907859b01419f720f604791fe8583a06620e
|
||||
sha256: e14b0bf45b71326654e2705d462f21b958f987087be850afd60578fcd502d1b8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "2.6.1"
|
||||
riverpod_generator:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: riverpod_generator
|
||||
sha256: "16569af989111e5087da6cfd71660eb0dbcfb87e5395cfa5181ce089ff4f7729"
|
||||
sha256: "44a0992d54473eb199ede00e2260bd3c262a86560e3c6f6374503d86d0580e36"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0-dev.17"
|
||||
version: "2.6.5"
|
||||
screen_retriever:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1461,10 +1453,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_gen
|
||||
sha256: "7b19d6ba131c6eb98bfcbf8d56c1a7002eba438af2e7ae6f8398b2b0f4f381e3"
|
||||
sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.0"
|
||||
version: "2.0.0"
|
||||
source_helper:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
25
pubspec.yaml
25
pubspec.yaml
@@ -19,7 +19,7 @@ dependencies:
|
||||
easy_isolate: ^1.3.0
|
||||
extended_image: ^10.0.0
|
||||
file_picker: ^10.1.9
|
||||
flutter_riverpod: ^3.0.0-dev.17
|
||||
flutter_riverpod: ^2.6.1
|
||||
flutter_highlight: ^0.7.0
|
||||
flutter_displaymode: ^0.6.0
|
||||
fl_chart: ^1.0.0
|
||||
@@ -30,7 +30,7 @@ dependencies:
|
||||
json_annotation: ^4.9.0
|
||||
responsive_framework: ^1.5.1
|
||||
re_editor: ^0.7.0
|
||||
riverpod_annotation: ^3.0.0-dev.17
|
||||
riverpod_annotation: ^2.6.1
|
||||
shared_preferences: ^2.1.1
|
||||
wakelock_plus: ^1.2.4
|
||||
wake_on_lan: ^4.1.1+3
|
||||
@@ -63,7 +63,7 @@ dependencies:
|
||||
fl_lib:
|
||||
git:
|
||||
url: https://github.com/lppcg/fl_lib
|
||||
ref: v1.0.338
|
||||
ref: v1.0.343
|
||||
flutter_gbk2utf8: ^1.0.1
|
||||
|
||||
dependency_overrides:
|
||||
@@ -90,10 +90,14 @@ dev_dependencies:
|
||||
flutter_lints: ^6.0.0
|
||||
json_serializable: ^6.8.0
|
||||
freezed: ^3.0.0
|
||||
riverpod_generator: ^3.0.0-dev.17
|
||||
riverpod_generator: ^2.6.1
|
||||
test: ^1.24.0
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
# riverpod_reg:
|
||||
# git:
|
||||
# url: https://github.com/lollipopkit/riverpod_reg
|
||||
# ref: v0.0.2
|
||||
fl_build:
|
||||
git:
|
||||
url: https://github.com/lppcg/fl_build.git
|
||||
@@ -120,18 +124,15 @@ flutter:
|
||||
# - asset: fonts/TrajanPro.ttf
|
||||
# - asset: fonts/TrajanPro_Bold.ttf
|
||||
# weight: 700
|
||||
|
||||
riverpod_reg:
|
||||
class_name: Riverpods
|
||||
gen_path: lib/generated/riverpod_reg.dart
|
||||
|
||||
flutter_native_splash:
|
||||
color: "#ffffff"
|
||||
image: assets/app_icon.png
|
||||
color_dark: "#121212"
|
||||
#background_image_dark: "assets/dark-background.png"
|
||||
#image_dark: assets/splash-invert.png
|
||||
#android: false
|
||||
#ios: false
|
||||
#web: false
|
||||
#android_gravity: center
|
||||
#ios_content_mode: center
|
||||
#fullscreen: true
|
||||
info_plist_files:
|
||||
- "ios/Runner/Info-Debug.plist"
|
||||
- "ios/Runner/Info-Profile.plist"
|
||||
|
||||
@@ -122,7 +122,7 @@ void main() {
|
||||
});
|
||||
|
||||
test('should handle Windows script path generation', () {
|
||||
final scriptPath = ShellFunc.status.exec('test-server', systemType: SystemType.windows);
|
||||
final scriptPath = ShellFunc.status.exec('test-server', systemType: SystemType.windows, customDir: null);
|
||||
|
||||
expect(scriptPath, contains('powershell'));
|
||||
expect(scriptPath, contains('-ExecutionPolicy Bypass'));
|
||||
@@ -131,7 +131,7 @@ void main() {
|
||||
|
||||
test('should execute Windows commands correctly', () {
|
||||
for (final func in ShellFunc.values) {
|
||||
final command = func.exec('test-server', systemType: SystemType.windows);
|
||||
final command = func.exec('test-server', systemType: SystemType.windows, customDir: null);
|
||||
expect(command, isNotEmpty);
|
||||
expect(command, contains('powershell'));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user