This commit is contained in:
lollipopkit🏳️‍⚧️
2025-08-31 23:59:53 +08:00
parent 5291d316a2
commit 3b7fdf36fb
32 changed files with 726 additions and 687 deletions

View File

@@ -2,7 +2,7 @@ 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/provider/server.dart';
import 'package:server_box/data/provider/server/single.dart';
import 'package:server_box/data/res/store.dart';
extension LogoExt on ServerState {

View File

@@ -104,6 +104,11 @@ Future<ServerStatus> _getLinuxStatus(ServerStatusUpdateReq req) async {
try {
req.ss.disk = Disk.parse(StatusCmdType.disk.findInMap(parsedOutput));
} catch (e, s) {
Loggers.app.warning(e, s);
}
try {
req.ss.diskUsage = DiskUsage.parse(req.ss.disk);
} catch (e, s) {
Loggers.app.warning(e, s);

View File

@@ -2,7 +2,7 @@ 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/server/all.dart';
import 'package:server_box/data/provider/sftp.dart';
import 'package:server_box/data/provider/snippet.dart';
@@ -45,7 +45,7 @@ final class ReadMyProvider {
T call<T>(ProviderBase<T> provider) => ref.read(provider);
// Specific provider getters
ServersState get server => ref.read(serverNotifierProvider);
ServersState get server => ref.read(serversNotifierProvider);
SnippetState get snippet => ref.read(snippetNotifierProvider);
AppState get app => ref.read(appStatesProvider);
PrivateKeyState get privateKey => ref.read(privateKeyNotifierProvider);
@@ -59,7 +59,7 @@ final class WatchMyProvider {
T call<T>(ProviderBase<T> provider) => ref.watch(provider);
// Specific provider getters
ServersState get server => ref.watch(serverNotifierProvider);
ServersState get server => ref.watch(serversNotifierProvider);
SnippetState get snippet => ref.watch(snippetNotifierProvider);
AppState get app => ref.watch(appStatesProvider);
PrivateKeyState get privateKey => ref.watch(privateKeyNotifierProvider);
@@ -74,7 +74,7 @@ final class UseNotifierMyProvider {
ref.read(provider.notifier);
// Specific provider notifier getters
ServerNotifier get server => ref.read(serverNotifierProvider.notifier);
ServersNotifier get server => ref.read(serversNotifierProvider.notifier);
SnippetNotifier get snippet => ref.read(snippetNotifierProvider.notifier);
AppStates get app => ref.read(appStatesProvider.notifier);
PrivateKeyNotifier get privateKey => ref.read(privateKeyNotifierProvider.notifier);

View File

@@ -13,7 +13,7 @@ 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';
import 'package:server_box/data/provider/server/single.dart';
part 'pve.freezed.dart';
part 'pve.g.dart';
@@ -45,7 +45,7 @@ class PveNotifier extends _$PveNotifier {
@override
PveState build(Spi spiParam) {
spi = spiParam;
final serverState = ref.watch(individualServerNotifierProvider(spi.id));
final serverState = ref.watch(serverNotifierProvider(spi.id));
final client = serverState.client;
if (client == null) {
return const PveState(error: PveErr(type: PveErrType.net, message: 'Server client is null'));

View File

@@ -6,7 +6,7 @@ part of 'pve.dart';
// RiverpodGenerator
// **************************************************************************
String _$pveNotifierHash() => r'667cfb11cd7118d57b29918d137ef2cda2bad7ad';
String _$pveNotifierHash() => r'b5da7240db1b9ee7d61f238cebca45821b7a3445';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -0,0 +1,273 @@
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/core/sync.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/try_limiter.dart';
import 'package:server_box/data/provider/server/single.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/ssh/session_manager.dart';
part 'all.freezed.dart';
part 'all.g.dart';
@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;
}
@Riverpod(keepAlive: true)
class ServersNotifier extends _$ServersNotifier {
@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(serverNotifierProvider(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(serverNotifierProvider(serverId));
if (serverState.conn != ServerConn.failed) return;
TryLimiter.reset(serverId);
}
if (state.manualDisconnectedIds.contains(serverId)) return;
final serverState = ref.read(serverNotifierProvider(serverId));
if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) {
return;
}
final serverNotifier = ref.read(serverNotifierProvider(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(serverNotifierProvider(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(serverNotifierProvider(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(serverNotifierProvider(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);
}
}

View File

@@ -3,7 +3,7 @@
// 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';
part of 'all.dart';
// **************************************************************************
// FreezedGenerator
@@ -304,291 +304,4 @@ 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

View File

@@ -0,0 +1,26 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'all.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$serversNotifierHash() => r'2ae641188f772794a32e8700c008f51ba0cc1ec9';
/// See also [ServersNotifier].
@ProviderFor(ServersNotifier)
final serversNotifierProvider =
NotifierProvider<ServersNotifier, ServersState>.internal(
ServersNotifier.new,
name: r'serversNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$serversNotifierHash,
dependencies: null,
allTransitiveDependencies: null,
);
typedef _$ServersNotifier = 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

View File

@@ -8,7 +8,6 @@ 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';
import 'package:server_box/core/utils/ssh_auth.dart';
import 'package:server_box/data/helper/system_detector.dart';
@@ -20,23 +19,13 @@ import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/server_status_update_req.dart';
import 'package:server_box/data/model/server/system.dart';
import 'package:server_box/data/model/server/try_limiter.dart';
import 'package:server_box/data/provider/server/all.dart';
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';
part 'server.freezed.dart';
part 'server.g.dart';
@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;
}
part 'single.g.dart';
part 'single.freezed.dart';
// Individual server state, including connection and status information
@freezed
@@ -50,20 +39,12 @@ abstract class ServerState with _$ServerState {
}) = _ServerState;
}
extension IndividualServerStateExtension on ServerState {
bool get needGenClient => conn < ServerConn.connecting;
bool get canViewDetails => conn == ServerConn.finished;
String get id => spi.id;
}
// Individual server state management
@riverpod
class IndividualServerNotifier extends _$IndividualServerNotifier {
class ServerNotifier extends _$ServerNotifier {
@override
ServerState build(String serverId) {
final serverNotifier = ref.read(serverNotifierProvider);
final serverNotifier = ref.read(serversNotifierProvider);
final spi = serverNotifier.servers[serverId];
if (spi == null) {
throw StateError('Server $serverId not found');
@@ -169,7 +150,7 @@ class IndividualServerNotifier extends _$IndividualServerNotifier {
id: sessionId,
spi: spi,
startTimeMs: time1.millisecondsSinceEpoch,
disconnect: () => ref.read(serverNotifierProvider.notifier)._closeOneServer(spi.id),
disconnect: () => ref.read(serversNotifierProvider.notifier).closeOneServer(spi.id),
status: TermSessionStatus.connecting,
);
TermSessionManager.setActive(sessionId, hasTerminal: false);
@@ -371,249 +352,10 @@ class IndividualServerNotifier extends _$IndividualServerNotifier {
}
}
@Riverpod(keepAlive: true)
class ServerNotifier extends _$ServerNotifier {
@override
ServersState build() {
// Initialize with empty state, load data asynchronously
Future.microtask(() => _load());
return const ServersState();
}
extension IndividualServerStateExtension on ServerState {
bool get needGenClient => conn < ServerConn.connecting;
Future<void> _load() async {
final spis = Stores.server.fetch();
final newServers = <String, Spi>{};
final newServerOrder = <String>[];
bool get canViewDetails => conn == ServerConn.finished;
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);
}
String get id => spi.id;
}

View File

@@ -0,0 +1,301 @@
// 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 'single.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
// dart format off
T _$identity<T>(T value) => value;
/// @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

View File

@@ -1,13 +1,12 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'server.dart';
part of 'single.dart';
// **************************************************************************
// RiverpodGenerator
// **************************************************************************
String _$individualServerNotifierHash() =>
r'e3d74fb95ca994cd8419b1deab743e8b3e21bee2';
String _$serverNotifierHash() => r'5625b0a4762c28efdbc124809c03b84a51d213b1';
/// Copied from Dart SDK
class _SystemHash {
@@ -30,30 +29,30 @@ class _SystemHash {
}
}
abstract class _$IndividualServerNotifier
abstract class _$ServerNotifier
extends BuildlessAutoDisposeNotifier<ServerState> {
late final String serverId;
ServerState build(String serverId);
}
/// See also [IndividualServerNotifier].
@ProviderFor(IndividualServerNotifier)
const individualServerNotifierProvider = IndividualServerNotifierFamily();
/// See also [ServerNotifier].
@ProviderFor(ServerNotifier)
const serverNotifierProvider = ServerNotifierFamily();
/// See also [IndividualServerNotifier].
class IndividualServerNotifierFamily extends Family<ServerState> {
/// See also [IndividualServerNotifier].
const IndividualServerNotifierFamily();
/// See also [ServerNotifier].
class ServerNotifierFamily extends Family<ServerState> {
/// See also [ServerNotifier].
const ServerNotifierFamily();
/// See also [IndividualServerNotifier].
IndividualServerNotifierProvider call(String serverId) {
return IndividualServerNotifierProvider(serverId);
/// See also [ServerNotifier].
ServerNotifierProvider call(String serverId) {
return ServerNotifierProvider(serverId);
}
@override
IndividualServerNotifierProvider getProviderOverride(
covariant IndividualServerNotifierProvider provider,
ServerNotifierProvider getProviderOverride(
covariant ServerNotifierProvider provider,
) {
return call(provider.serverId);
}
@@ -70,29 +69,28 @@ class IndividualServerNotifierFamily extends Family<ServerState> {
_allTransitiveDependencies;
@override
String? get name => r'individualServerNotifierProvider';
String? get name => r'serverNotifierProvider';
}
/// See also [IndividualServerNotifier].
class IndividualServerNotifierProvider
extends
AutoDisposeNotifierProviderImpl<IndividualServerNotifier, ServerState> {
/// See also [IndividualServerNotifier].
IndividualServerNotifierProvider(String serverId)
/// See also [ServerNotifier].
class ServerNotifierProvider
extends AutoDisposeNotifierProviderImpl<ServerNotifier, ServerState> {
/// See also [ServerNotifier].
ServerNotifierProvider(String serverId)
: this._internal(
() => IndividualServerNotifier()..serverId = serverId,
from: individualServerNotifierProvider,
name: r'individualServerNotifierProvider',
() => ServerNotifier()..serverId = serverId,
from: serverNotifierProvider,
name: r'serverNotifierProvider',
debugGetCreateSourceHash: const bool.fromEnvironment('dart.vm.product')
? null
: _$individualServerNotifierHash,
dependencies: IndividualServerNotifierFamily._dependencies,
: _$serverNotifierHash,
dependencies: ServerNotifierFamily._dependencies,
allTransitiveDependencies:
IndividualServerNotifierFamily._allTransitiveDependencies,
ServerNotifierFamily._allTransitiveDependencies,
serverId: serverId,
);
IndividualServerNotifierProvider._internal(
ServerNotifierProvider._internal(
super._createNotifier, {
required super.name,
required super.dependencies,
@@ -105,15 +103,15 @@ class IndividualServerNotifierProvider
final String serverId;
@override
ServerState runNotifierBuild(covariant IndividualServerNotifier notifier) {
ServerState runNotifierBuild(covariant ServerNotifier notifier) {
return notifier.build(serverId);
}
@override
Override overrideWith(IndividualServerNotifier Function() create) {
Override overrideWith(ServerNotifier Function() create) {
return ProviderOverride(
origin: this,
override: IndividualServerNotifierProvider._internal(
override: ServerNotifierProvider._internal(
() => create()..serverId = serverId,
from: from,
name: null,
@@ -126,15 +124,14 @@ class IndividualServerNotifierProvider
}
@override
AutoDisposeNotifierProviderElement<IndividualServerNotifier, ServerState>
AutoDisposeNotifierProviderElement<ServerNotifier, ServerState>
createElement() {
return _IndividualServerNotifierProviderElement(this);
return _ServerNotifierProviderElement(this);
}
@override
bool operator ==(Object other) {
return other is IndividualServerNotifierProvider &&
other.serverId == serverId;
return other is ServerNotifierProvider && other.serverId == serverId;
}
@override
@@ -148,40 +145,19 @@ class IndividualServerNotifierProvider
@Deprecated('Will be removed in 3.0. Use Ref instead')
// ignore: unused_element
mixin IndividualServerNotifierRef
on AutoDisposeNotifierProviderRef<ServerState> {
mixin ServerNotifierRef on AutoDisposeNotifierProviderRef<ServerState> {
/// The parameter `serverId` of this provider.
String get serverId;
}
class _IndividualServerNotifierProviderElement
extends
AutoDisposeNotifierProviderElement<
IndividualServerNotifier,
ServerState
>
with IndividualServerNotifierRef {
_IndividualServerNotifierProviderElement(super.provider);
class _ServerNotifierProviderElement
extends AutoDisposeNotifierProviderElement<ServerNotifier, ServerState>
with ServerNotifierRef {
_ServerNotifierProviderElement(super.provider);
@override
String get serverId => (origin as IndividualServerNotifierProvider).serverId;
String get serverId => (origin as ServerNotifierProvider).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

View File

@@ -5,7 +5,7 @@ import 'package:server_box/core/extension/ssh_client.dart';
import 'package:server_box/data/model/app/scripts/script_consts.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/systemd.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/server/single.dart';
part 'systemd.freezed.dart';
part 'systemd.g.dart';
@@ -25,7 +25,7 @@ class SystemdNotifier extends _$SystemdNotifier {
@override
SystemdState build(Spi spi) {
final si = ref.read(individualServerNotifierProvider(spi.id));
final si = ref.read(serverNotifierProvider(spi.id));
_si = si;
// Async initialization
Future.microtask(() => getUnits());

View File

@@ -6,7 +6,7 @@ part of 'systemd.dart';
// RiverpodGenerator
// **************************************************************************
String _$systemdNotifierHash() => r'617fb7637fbc5c5100e5b522d246984f22b44cca';
String _$systemdNotifierHash() => r'98466bd176518545be49cae52f8dbe12af3a88a6';
/// Copied from Dart SDK
class _SystemHash {

View File

@@ -13,7 +13,7 @@ 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/provider/container.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/server/single.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/ssh/page/page.dart';
@@ -43,7 +43,7 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
@override
void initState() {
super.initState();
final serverState = ref.read(individualServerNotifierProvider(widget.args.spi.id));
final serverState = ref.read(serverNotifierProvider(widget.args.spi.id));
_provider = containerNotifierProvider(
serverState.client,
widget.args.spi.user,

View File

@@ -5,7 +5,7 @@ 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/provider/server/all.dart';
import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/res/url.dart';
@@ -31,8 +31,8 @@ class _HomePageState extends ConsumerState<HomePage>
bool _shouldAuth = false;
DateTime? _pausedTime;
late final _notifier = ref.read(serverNotifierProvider.notifier);
late final _provider = ref.read(serverNotifierProvider);
late final _notifier = ref.read(serversNotifierProvider.notifier);
late final _provider = ref.read(serversNotifierProvider);
@override
void dispose() {

View File

@@ -5,7 +5,8 @@ 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';
import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/data/provider/server/single.dart';
/// Only permit ipv4 / ipv6 / domain chars
final targetReg = RegExp(r'[a-zA-Z0-9\.-_:]+');
@@ -130,7 +131,7 @@ class _PingPageState extends ConsumerState<PingPage> with AutomaticKeepAliveClie
return;
}
if (ref.read(serverNotifierProvider).serverOrder.isEmpty) {
if (ref.read(serversNotifierProvider).serverOrder.isEmpty) {
context.showSnackBar(l10n.pingNoServer);
return;
}
@@ -142,8 +143,8 @@ class _PingPageState extends ConsumerState<PingPage> with AutomaticKeepAliveClie
}
await Future.wait(
ref.read(serverNotifierProvider).servers.values.map((spi) async {
final serverState = ref.read(individualServerNotifierProvider(spi.id));
ref.read(serversNotifierProvider).servers.values.map((spi) async {
final serverState = ref.read(serverNotifierProvider(spi.id));
if (serverState.client == null) {
return;
}

View File

@@ -8,7 +8,7 @@ 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/provider/server.dart';
import 'package:server_box/data/provider/server/single.dart';
import 'package:server_box/data/res/store.dart';
class ProcessPage extends ConsumerStatefulWidget {
@@ -37,7 +37,7 @@ class _ProcessPageState extends ConsumerState<ProcessPage> {
ProcSortMode _procSortMode = ProcSortMode.cpu;
List<ProcSortMode> _sortModes = List.from(ProcSortMode.values);
late final _provider = individualServerNotifierProvider(widget.args.spi.id);
late final _provider = serverNotifierProvider(widget.args.spi.id);
@override
void dispose() {

View File

@@ -21,7 +21,7 @@ import 'package:server_box/data/model/server/sensors.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/provider/server/single.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';
@@ -86,7 +86,7 @@ class _ServerDetailPageState extends ConsumerState<ServerDetailPage> with Single
@override
Widget build(BuildContext context) {
final serverState = ref.watch(individualServerNotifierProvider(widget.args.spi.id));
final serverState = ref.watch(serverNotifierProvider(widget.args.spi.id));
if (serverState.client == null) {
return Scaffold(
appBar: CustomAppBar(),

View File

@@ -17,7 +17,7 @@ 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';
import 'package:server_box/data/provider/private_key.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/data/store/server.dart';
import 'package:server_box/view/page/private_key/edit.dart';
@@ -172,7 +172,7 @@ class _ServerEditPageState extends ConsumerState<ServerEditPage> with AfterLayou
hint: 'root',
suggestion: false,
),
TagTile(tags: _tags, allTags: ref.watch(serverNotifierProvider).tags).cardx,
TagTile(tags: _tags, allTags: ref.watch(serversNotifierProvider).tags).cardx,
ListTile(
title: Text(l10n.autoConnect),
trailing: _autoConnect.listenVal(
@@ -492,7 +492,7 @@ class _ServerEditPageState extends ConsumerState<ServerEditPage> with AfterLayou
Widget _buildJumpServer() {
const padding = EdgeInsets.only(left: 13, right: 13, bottom: 7);
final srvs = ref
.watch(serverNotifierProvider)
.watch(serversNotifierProvider)
.servers
.values
.where((e) => e.jumpId == null)
@@ -587,7 +587,7 @@ class _ServerEditPageState extends ConsumerState<ServerEditPage> with AfterLayou
actions: Btn.ok(
onTap: () async {
context.pop();
ref.read(serverNotifierProvider.notifier).delServer(spi!.id);
ref.read(serversNotifierProvider.notifier).delServer(spi!.id);
context.pop(true);
},
red: true,
@@ -683,7 +683,7 @@ extension _Actions on _ServerEditPageState {
if (shouldImport == true) {
for (final server in resolved) {
ref.read(serverNotifierProvider.notifier).addServer(server);
ref.read(serversNotifierProvider.notifier).addServer(server);
}
context.showSnackBar(l10n.sshConfigImported('${resolved.length}'));
}
@@ -802,9 +802,9 @@ extension _Actions on _ServerEditPageState {
context.showSnackBar('${l10n.sameIdServerExist}: ${spi.id}');
return;
}
ref.read(serverNotifierProvider.notifier).addServer(spi);
ref.read(serversNotifierProvider.notifier).addServer(spi);
} else {
ref.read(serverNotifierProvider.notifier).updateServer(this.spi!, spi);
ref.read(serversNotifierProvider.notifier).updateServer(this.spi!, spi);
}
context.pop();
@@ -860,7 +860,7 @@ extension _Utils on _ServerEditPageState {
// Import without asking again since user already gave permission
for (final server in resolved) {
ref.read(serverNotifierProvider.notifier).addServer(server);
ref.read(serversNotifierProvider.notifier).addServer(server);
}
context.showSnackBar(l10n.sshConfigImported('${resolved.length}'));
}

View File

@@ -30,16 +30,16 @@ extension on _ServerPageState {
const Icon(Icons.refresh, size: 21, color: Colors.grey),
() {
TryLimiter.reset(s.spi.id);
ref.read(serverNotifierProvider.notifier).refresh(spi: s.spi);
ref.read(serversNotifierProvider.notifier).refresh(spi: s.spi);
},
),
ServerConn.disconnected => (
const Icon(MingCute.link_3_line, size: 19, color: Colors.grey),
() => ref.read(serverNotifierProvider.notifier).refresh(spi: s.spi),
() => ref.read(serversNotifierProvider.notifier).refresh(spi: s.spi),
),
ServerConn.finished => (
const Icon(MingCute.unlink_2_line, size: 17, color: Colors.grey),
() => ref.read(serverNotifierProvider.notifier).closeServer(id: s.spi.id),
() => ref.read(serversNotifierProvider.notifier).closeServer(id: s.spi.id),
),
};
@@ -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 = ref.watch(serverNotifierProvider).servers[id]?.custom?.netDev;
final device = ref.watch(serversNotifierProvider).servers[id]?.custom?.netDev;
final (a, b) = type.build(ss, dev: device);
return AnimatedSwitcher(
duration: const Duration(milliseconds: 377),

View File

@@ -26,7 +26,7 @@ extension on _ServerPageState {
}
Widget _buildLandscapeBody() {
final serverState = ref.watch(serverNotifierProvider);
final serverState = ref.watch(serversNotifierProvider);
final order = serverState.serverOrder;
if (order.isEmpty) {
@@ -37,7 +37,7 @@ extension on _ServerPageState {
itemCount: order.length,
itemBuilder: (_, idx) {
final id = order[idx];
final srv = ref.watch(individualServerNotifierProvider(id));
final srv = ref.watch(serverNotifierProvider(id));
final title = _buildServerCardTitle(srv);
final List<Widget> children = [title, _buildNormalCard(srv.status, srv.spi)];

View File

@@ -17,7 +17,8 @@ import 'package:server_box/data/model/app/scripts/shell_func.dart';
import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
import 'package:server_box/data/model/server/try_limiter.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/data/provider/server/single.dart';
import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/server/detail/view.dart';
@@ -73,7 +74,7 @@ class _ServerPageState extends ConsumerState<ServerPage>
@override
void initState() {
super.initState();
_tags = ValueNotifier(ref.read(serverNotifierProvider).tags);
_tags = ValueNotifier(ref.read(serversNotifierProvider).tags);
_startAvoidJitterTimer();
}
@@ -88,7 +89,7 @@ class _ServerPageState extends ConsumerState<ServerPage>
Widget build(BuildContext context) {
super.build(context);
// Listen to provider changes and update the ValueNotifier
ref.listen(serverNotifierProvider, (previous, next) {
ref.listen(serversNotifierProvider, (previous, next) {
_tags.value = next.tags;
});
return OrientationBuilder(
@@ -132,7 +133,7 @@ class _ServerPageState extends ConsumerState<ServerPage>
Widget _buildPortrait() {
// final isMobile = ResponsiveBreakpoints.of(context).isMobile;
final serverState = ref.watch(serverNotifierProvider);
final serverState = ref.watch(serversNotifierProvider);
return _tag.listenVal((val) {
final filtered = _filterServers(serverState.serverOrder);
final child = _buildScaffold(_buildBodySmall(filtered: filtered));
@@ -182,7 +183,7 @@ class _ServerPageState extends ConsumerState<ServerPage>
// Last item is just spacing
if (index == lens) return SizedBox(height: 77);
final individualState = ref.watch(individualServerNotifierProvider(serversInThisColumn[index]));
final individualState = ref.watch(serverNotifierProvider(serversInThisColumn[index]));
return _buildEachServerCard(individualState);
},
@@ -338,8 +339,8 @@ class _ServerPageState extends ConsumerState<ServerPage>
@override
Future<void> afterFirstLayout(BuildContext context) async {
ref.read(serverNotifierProvider.notifier).refresh();
ref.read(serverNotifierProvider.notifier).startAutoRefresh();
ref.read(serversNotifierProvider.notifier).refresh();
ref.read(serversNotifierProvider.notifier).startAutoRefresh();
}
static const _kCardHeightMin = 23.0;

View File

@@ -98,7 +98,7 @@ extension _Utils on _ServerPageState {
final tag = _tag.value;
if (tag == TagSwitcher.kDefaultTag) return order;
return order.where((e) {
final tags = ref.read(serverNotifierProvider).servers[e]?.tags;
final tags = ref.read(serversNotifierProvider).servers[e]?.tags;
if (tags == null) return false;
return tags.contains(tag);
}).toList();

View File

@@ -46,7 +46,7 @@ extension _Server on _AppSettingsPageState {
onTap: () async {
final keys = Stores.server.keys();
final names = Map.fromEntries(
keys.map((e) => MapEntry(e, ref.read(serverNotifierProvider).servers[e]?.name ?? e)),
keys.map((e) => MapEntry(e, ref.read(serversNotifierProvider).servers[e]?.name ?? e)),
);
final deleteKeys = await context.showPickDialog<String>(
clearable: true,

View File

@@ -8,7 +8,7 @@ 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';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/data/res/build_data.dart';
import 'package:server_box/data/res/github_id.dart';
import 'package:server_box/data/res/store.dart';

View File

@@ -4,7 +4,7 @@ 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/provider/server/all.dart';
import 'package:server_box/data/res/store.dart';
class ServerOrderPage extends ConsumerStatefulWidget {
@@ -42,7 +42,7 @@ class _ServerOrderPageState extends ConsumerState<ServerOrderPage> {
}
Widget _buildBody() {
final serverState = ref.watch(serverNotifierProvider);
final serverState = ref.watch(serversNotifierProvider);
final order = serverState.serverOrder;
if (order.isEmpty) {
@@ -77,7 +77,7 @@ class _ServerOrderPageState extends ConsumerState<ServerOrderPage> {
}
Widget _buildCardTile(int index) {
final serverState = ref.watch(serverNotifierProvider);
final serverState = ref.watch(serversNotifierProvider);
final order = serverState.serverOrder;
final id = order[index];
final spi = serverState.servers[id];

View File

@@ -4,7 +4,7 @@ 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';
import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/data/provider/snippet.dart';
final class SnippetEditPageArgs {
@@ -157,7 +157,7 @@ class _SnippetEditPageState extends ConsumerState<SnippetEditPage> with AfterLay
builder: (vals) {
final subtitle = vals.isEmpty
? null
: vals.map((e) => ref.read(serverNotifierProvider).servers[e]?.name ?? e).join(', ');
: vals.map((e) => ref.read(serversNotifierProvider).servers[e]?.name ?? e).join(', ');
return ListTile(
leading: const Padding(
padding: EdgeInsets.only(left: 5),
@@ -169,12 +169,13 @@ class _SnippetEditPageState extends ConsumerState<SnippetEditPage> with AfterLay
? null
: Text(subtitle, maxLines: 1, style: UIs.textGrey, overflow: TextOverflow.ellipsis),
onTap: () async {
vals.removeWhere((e) => !ref.read(serverNotifierProvider).serverOrder.contains(e));
// Create a filtered copy for the dialog, don't modify the original
final validServerIds = vals.where((e) => ref.read(serversNotifierProvider).serverOrder.contains(e)).toList();
final serverIds = await context.showPickDialog(
title: l10n.autoRun,
items: ref.read(serverNotifierProvider).serverOrder,
display: (e) => ref.read(serverNotifierProvider).servers[e]?.name ?? e,
initial: vals,
items: ref.read(serversNotifierProvider).serverOrder,
display: (e) => ref.read(serversNotifierProvider).servers[e]?.name ?? e,
initial: validServerIds,
clearable: true,
);
if (serverIds != null) {

View File

@@ -16,7 +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/server/single.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';
@@ -118,7 +118,7 @@ class SSHPageState extends ConsumerState<SSHPage>
_setupDiscontinuityTimer();
// Initialize client from provider
final serverState = ref.read(individualServerNotifierProvider(widget.args.spi.id));
final serverState = ref.read(serverNotifierProvider(widget.args.spi.id));
_client = serverState.client;
if (++_sshConnCount == 1) {

View File

@@ -7,7 +7,7 @@ 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';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/view/page/server/edit.dart';
import 'package:server_box/view/page/ssh/page/page.dart';
@@ -249,7 +249,7 @@ class _AddPage extends ConsumerWidget {
const viewPadding = 7.0;
final viewWidth = context.windowSize.width - 2 * viewPadding;
final serverState = ref.watch(serverNotifierProvider);
final serverState = ref.watch(serversNotifierProvider);
final itemCount = serverState.servers.length;
const itemPadding = 1.0;
const itemWidth = 150.0;

View File

@@ -7,7 +7,7 @@ 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';
import 'package:server_box/data/model/sftp/worker.dart';
import 'package:server_box/data/provider/server.dart';
import 'package:server_box/data/provider/server/all.dart';
import 'package:server_box/data/provider/sftp.dart';
import 'package:server_box/data/res/misc.dart';
import 'package:server_box/data/store/setting.dart';
@@ -359,7 +359,7 @@ extension _OnTapFile on _LocalFilePageState {
final spi = await context.showPickSingleDialog<Spi>(
title: libL10n.select,
items: ref.read(serverNotifierProvider).servers.values.toList(),
items: ref.read(serversNotifierProvider).servers.values.toList(),
display: (e) => e.name,
);
if (spi == null) return;

View File

@@ -12,7 +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/server/single.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';
@@ -50,7 +50,7 @@ class _SftpPageState extends ConsumerState<SftpPage> with AfterLayoutMixin {
@override
void initState() {
super.initState();
final serverState = ref.read(individualServerNotifierProvider(widget.args.spi.id));
final serverState = ref.read(serverNotifierProvider(widget.args.spi.id));
_client = serverState.client!;
_status = SftpBrowserStatus(_client);
}

View File

@@ -10,7 +10,7 @@ import 'package:server_box/data/model/app/menu/base.dart';
import 'package:server_box/data/model/app/menu/server_func.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/provider/server.dart';
import 'package:server_box/data/provider/server/single.dart';
import 'package:server_box/data/provider/snippet.dart';
import 'package:server_box/data/res/store.dart';
import 'package:server_box/view/page/container/container.dart';
@@ -273,7 +273,7 @@ void _gotoSSH(Spi spi, BuildContext context) async {
}
bool _checkClient(BuildContext context, String id, WidgetRef ref) {
final serverState = ref.read(individualServerNotifierProvider(id));
final serverState = ref.read(serverNotifierProvider(id));
if (serverState.client == null) {
context.showSnackBar(l10n.waitConnection);
return false;