Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions lib/core/utils/refresh_interval.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import 'package:server_box/data/res/default.dart';
import 'package:server_box/data/res/store.dart';

int? normalizeServerStatusRefreshSeconds(int seconds) {
if (seconds == 0) return null;
if (seconds <= 1 || seconds > 10) return Defaults.updateInterval;
return seconds;
}

Duration? serverStatusRefreshInterval() {
final seconds = normalizeServerStatusRefreshSeconds(
Stores.setting.serverStatusUpdateInterval.fetch(),
);
return seconds == null ? null : Duration(seconds: seconds);
}
25 changes: 25 additions & 0 deletions lib/core/utils/version.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
List<int>? parseVersionParts(String raw) {
final match = RegExp(r'(\d+)(?:\.(\d+))?(?:\.(\d+))?').firstMatch(raw);
if (match == null) return null;
final major = int.tryParse(match.group(1)!);
if (major == null) return null;
return [
major,
int.tryParse(match.group(2) ?? '') ?? 0,
int.tryParse(match.group(3) ?? '') ?? 0,
];
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

bool isVersionLessThan(String raw, List<int> minimum) {
final version = parseVersionParts(raw);
if (version == null) return false;

for (var i = 0; i < minimum.length; i++) {
final currentPart = i < version.length ? version[i] : 0;
final minimumPart = minimum[i];
if (currentPart != minimumPart) {
return currentPart < minimumPart;
}
}
return false;
}
63 changes: 47 additions & 16 deletions lib/data/provider/server/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ 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/core/utils/refresh_interval.dart';
import 'package:server_box/core/utils/sudo_password.dart';
import 'package:server_box/data/model/server/server.dart';
import 'package:server_box/data/model/server/server_private_info.dart';
Expand Down Expand Up @@ -65,8 +66,16 @@ class ServersNotifier extends _$ServersNotifier {

final newTags = _calculateTags(newServers);

return stateOrNull?.copyWith(servers: newServers, serverOrder: newServerOrder, tags: newTags) ??
ServersState(servers: newServers, serverOrder: newServerOrder, tags: newTags);
return stateOrNull?.copyWith(
servers: newServers,
serverOrder: newServerOrder,
tags: newTags,
) ??
ServersState(
servers: newServers,
serverOrder: newServerOrder,
tags: newTags,
);
}

Set<String> _calculateTags(Map<String, Spi> servers) {
Expand All @@ -85,7 +94,11 @@ class ServersNotifier extends _$ServersNotifier {
try {
await SudoPassword.clearOverride(id);
} catch (e, s) {
Loggers.app.warning('Failed to clear sudo password override for server $id', e, s);
Loggers.app.warning(
'Failed to clear sudo password override for server $id',
e,
s,
);
}
}

Expand All @@ -106,7 +119,9 @@ class ServersNotifier extends _$ServersNotifier {
/// [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);
final newManualDisconnected = Set<String>.from(
state.manualDisconnectedIds,
)..remove(spi.id);
state = state.copyWith(manualDisconnectedIds: newManualDisconnected);
final serverNotifier = ref.read(serverProvider(spi.id).notifier);
await serverNotifier.refresh();
Expand All @@ -125,11 +140,15 @@ class ServersNotifier extends _$ServersNotifier {
final serverState = ref.read(serverProvider(serverId));

if (onlyFailed) {
if (serverState.conn != ServerConn.failed) continue;
if (serverState.conn != ServerConn.failed) {
continue;
}
idsToResetLimiter.add(serverId);
}

if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) continue;
if (serverState.conn == ServerConn.disconnected && !spi.autoConnect) {
continue;
}

serversToRefresh.add(entry);
}
Expand All @@ -145,12 +164,16 @@ class ServersNotifier extends _$ServersNotifier {
}

Future<void> startAutoRefresh() async {
var duration = Stores.setting.serverStatusUpdateInterval.fetch();
stopAutoRefresh();
if (duration == 0) return;
if (duration <= 1 || duration > 10) {
Loggers.app.warning('Invalid duration: $duration, use default 3');
duration = 3;
final rawDuration = Stores.setting.serverStatusUpdateInterval.fetch();
final duration = normalizeServerStatusRefreshSeconds(rawDuration);
if (duration == null) {
return;
}
if (duration != rawDuration) {
Loggers.app.warning(
'Invalid duration: $rawDuration, use default $duration',
);
}
final timer = Timer.periodic(Duration(seconds: duration), (_) async {
await refresh();
Expand All @@ -175,7 +198,10 @@ class ServersNotifier extends _$ServersNotifier {

// Update SSH session status to disconnected
final sessionId = 'ssh_$serverId';
TermSessionManager.updateStatus(sessionId, TermSessionStatus.disconnected);
TermSessionManager.updateStatus(
sessionId,
TermSessionStatus.disconnected,
);
}
//TryLimiter.clear();
}
Expand All @@ -200,7 +226,8 @@ class ServersNotifier extends _$ServersNotifier {
final serverNotifier = ref.read(serverProvider(id).notifier);
serverNotifier.closeConnection();

final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..add(id);
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)
..add(id);
state = state.copyWith(manualDisconnectedIds: newManualDisconnected);

// Remove SSH session when server is manually closed
Expand All @@ -216,7 +243,8 @@ class ServersNotifier extends _$ServersNotifier {

final newOrder = List<String>.from(state.serverOrder)..add(spi.id);
final newTags = _calculateTags(newServers);
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..remove(spi.id);
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)
..remove(spi.id);

state = state.copyWith(
servers: newServers,
Expand All @@ -237,7 +265,8 @@ class ServersNotifier extends _$ServersNotifier {

final newOrder = List<String>.from(state.serverOrder)..remove(id);
final newTags = _calculateTags(newServers);
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)..remove(id);
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds)
..remove(id);

state = state.copyWith(
servers: newServers,
Expand Down Expand Up @@ -329,7 +358,9 @@ class ServersNotifier extends _$ServersNotifier {

final newServers = Map<String, Spi>.from(state.servers);
final newOrder = List<String>.from(state.serverOrder);
final newManualDisconnected = Set<String>.from(state.manualDisconnectedIds);
final newManualDisconnected = Set<String>.from(
state.manualDisconnectedIds,
);

if (newSpi.id != old.id) {
newServers[newSpi.id] = newSpi;
Expand Down
105 changes: 79 additions & 26 deletions lib/view/page/container/actions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,11 @@ extension on _ContainerPageState {
/// Watch the current state of the container.
ContainerState get _containerState => ref.watch(_provider);

String _errorMessage(String? message) {
final trimmed = message?.trim();
return trimmed?.isNotEmpty == true ? trimmed! : libL10n.fail;
}

Future<void> _showAddFAB() async {
final imageCtrl = TextEditingController();
final nameCtrl = TextEditingController();
Expand Down Expand Up @@ -44,7 +49,11 @@ extension on _ContainerPageState {
onTap: () async {
context.pop();
await _showAddCmdPreview(
_buildAddCmd(imageCtrl.text.trim(), nameCtrl.text.trim(), argsCtrl.text.trim()),
_buildAddCmd(
imageCtrl.text.trim(),
nameCtrl.text.trim(),
argsCtrl.text.trim(),
),
);
},
).toList,
Expand All @@ -65,7 +74,10 @@ extension on _ContainerPageState {
final (result, err) = await context.showLoadingDialog(fn: onConfirm);
if (err != null || result != null) {
final e = result?.message ?? err?.toString();
context.showRoundDialog(title: libL10n.error, child: Text(e.toString()));
context.showRoundDialog(
title: libL10n.error,
child: Text(_errorMessage(e)),
);
} else {
context.showSnackBar(libL10n.success);
}
Expand All @@ -85,10 +97,15 @@ extension on _ContainerPageState {
onPressed: () async {
context.pop();

final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.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()));
context.showRoundDialog(
title: libL10n.error,
child: Text(_errorMessage(e)),
);
}
},
child: Text(libL10n.run),
Expand Down Expand Up @@ -123,13 +140,15 @@ extension on _ContainerPageState {
void _showImageRmDialog(ContainerImg e) {
context.showRoundDialog(
title: libL10n.attention,
child: Text(libL10n.askContinue('${libL10n.delete} Image(${e.repository})')),
child: Text(
libL10n.askContinue('${libL10n.delete} Image(${e.repository})'),
),
actions: Btn.ok(
onTap: () async {
context.pop();
final result = await _containerNotifier.run('rmi ${e.id} -f');
if (result != null) {
context.showSnackBar(result.message ?? 'null');
context.showSnackBar(_errorMessage(result.message));
}
},
red: true,
Expand All @@ -149,7 +168,9 @@ extension on _ContainerPageState {
final imageRef = '$repo:$tag';
context.showRoundDialog(
title: libL10n.attention,
child: Text(libL10n.askContinue('${l10n.pull} ${l10n.image}($imageRef)')),
child: Text(
libL10n.askContinue('${l10n.pull} ${l10n.image}($imageRef)'),
),
actions: Btn.ok(
onTap: () async {
context.pop();
Expand All @@ -160,7 +181,10 @@ extension on _ContainerPageState {
);
if (err != null || result != null) {
final e = result?.message ?? err?.toString();
context.showRoundDialog(title: libL10n.error, child: Text(e ?? 'null'));
context.showRoundDialog(
title: libL10n.error,
child: Text(_errorMessage(e)),
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
},
).toList,
Expand All @@ -186,13 +210,21 @@ extension on _ContainerPageState {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(libL10n.askContinue('${libL10n.delete} Container(${dItem.name})')),
Text(
libL10n.askContinue(
'${libL10n.delete} Container(${dItem.name})',
),
),
UIs.height13,
Row(
children: [
StatefulBuilder(
builder: (_, setState) {
return Checkbox(value: force, onChanged: (val) => setState(() => force = val ?? false));
return Checkbox(
value: force,
onChanged: (val) =>
setState(() => force = val ?? false),
);
},
),
Text(libL10n.force),
Expand All @@ -209,31 +241,49 @@ extension on _ContainerPageState {
);
if (err != null || result != null) {
final e = result?.message ?? err?.toString();
context.showRoundDialog(title: libL10n.error, child: Text(e ?? 'null'));
context.showRoundDialog(
title: libL10n.error,
child: Text(_errorMessage(e)),
);
}
},
).toList,
);
break;
case ContainerMenu.start:
final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.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'));
context.showRoundDialog(
title: libL10n.error,
child: Text(_errorMessage(e)),
);
}
break;
case ContainerMenu.stop:
final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.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'));
context.showRoundDialog(
title: libL10n.error,
child: Text(_errorMessage(e)),
);
}
break;
case ContainerMenu.restart:
final (result, err) = await context.showLoadingDialog(fn: () => _containerNotifier.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'));
context.showRoundDialog(
title: libL10n.error,
child: Text(_errorMessage(e)),
);
}
break;
case ContainerMenu.logs:
Expand Down Expand Up @@ -262,14 +312,17 @@ extension on _ContainerPageState {
}

void _initAutoRefresh() {
if (Stores.setting.containerAutoRefresh.fetch()) {
Timer.periodic(Duration(seconds: Stores.setting.serverStatusUpdateInterval.fetch()), (timer) {
if (mounted) {
_containerNotifier.refresh(isAuto: true);
} else {
timer.cancel();
}
});
}
_autoRefreshTimer?.cancel();
_autoRefreshTimer = null;
if (!Stores.setting.containerAutoRefresh.fetch()) return;
final duration = serverStatusRefreshInterval();
if (duration == null) return;
_autoRefreshTimer = Timer.periodic(duration, (timer) {
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if (mounted) {
_containerNotifier.refresh(isAuto: true);
} else {
timer.cancel();
}
});
}
}
Loading