mirror of
https://github.com/lollipopkit/flutter_server_box.git
synced 2025-12-16 14:54:34 +01:00
refactor: docker status parsing (#886)
This commit is contained in:
@@ -2,6 +2,7 @@ import 'dart:convert';
|
||||
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
import 'package:server_box/data/model/container/status.dart';
|
||||
import 'package:server_box/data/model/container/type.dart';
|
||||
import 'package:server_box/data/res/misc.dart';
|
||||
|
||||
@@ -10,7 +11,7 @@ sealed class ContainerPs {
|
||||
final String? image = null;
|
||||
String? get name;
|
||||
String? get cmd;
|
||||
bool get running;
|
||||
ContainerStatus get status;
|
||||
|
||||
String? cpu;
|
||||
String? mem;
|
||||
@@ -51,7 +52,7 @@ final class PodmanPs implements ContainerPs {
|
||||
String? get cmd => command?.firstOrNull;
|
||||
|
||||
@override
|
||||
bool get running => exited != true;
|
||||
ContainerStatus get status => ContainerStatus.fromPodmanExited(exited);
|
||||
|
||||
@override
|
||||
void parseStats(String s) {
|
||||
@@ -121,10 +122,7 @@ final class DockerPs implements ContainerPs {
|
||||
String? get cmd => null;
|
||||
|
||||
@override
|
||||
bool get running {
|
||||
if (state?.contains('Exited') == true) return false;
|
||||
return true;
|
||||
}
|
||||
ContainerStatus get status => ContainerStatus.fromDockerState(state);
|
||||
|
||||
@override
|
||||
void parseStats(String s) {
|
||||
|
||||
70
lib/data/model/container/status.dart
Normal file
70
lib/data/model/container/status.dart
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'package:fl_lib/fl_lib.dart';
|
||||
import 'package:server_box/core/extension/context/locale.dart';
|
||||
|
||||
/// Represents the various states a container can be in.
|
||||
/// Supports both Docker and Podman container status parsing.
|
||||
enum ContainerStatus {
|
||||
running,
|
||||
exited,
|
||||
created,
|
||||
paused,
|
||||
restarting,
|
||||
removing,
|
||||
dead,
|
||||
unknown;
|
||||
|
||||
/// Check if the container is actively running
|
||||
bool get isRunning => this == ContainerStatus.running;
|
||||
|
||||
/// Check if the container can be started
|
||||
bool get canStart =>
|
||||
this == ContainerStatus.exited ||
|
||||
this == ContainerStatus.created ||
|
||||
this == ContainerStatus.dead;
|
||||
|
||||
/// Check if the container can be stopped
|
||||
bool get canStop =>
|
||||
this == ContainerStatus.running || this == ContainerStatus.paused;
|
||||
|
||||
/// Check if the container can be restarted
|
||||
bool get canRestart =>
|
||||
this != ContainerStatus.removing && this != ContainerStatus.unknown;
|
||||
|
||||
/// Parse Docker container status string to ContainerStatus
|
||||
static ContainerStatus fromDockerState(String? state) {
|
||||
if (state == null || state.isEmpty) return ContainerStatus.unknown;
|
||||
|
||||
final lowerState = state.toLowerCase();
|
||||
|
||||
if (lowerState.startsWith('up')) return ContainerStatus.running;
|
||||
if (lowerState.contains('exited')) return ContainerStatus.exited;
|
||||
if (lowerState.contains('created')) return ContainerStatus.created;
|
||||
if (lowerState.contains('paused')) return ContainerStatus.paused;
|
||||
if (lowerState.contains('restarting')) return ContainerStatus.restarting;
|
||||
if (lowerState.contains('removing')) return ContainerStatus.removing;
|
||||
if (lowerState.contains('dead')) return ContainerStatus.dead;
|
||||
|
||||
return ContainerStatus.unknown;
|
||||
}
|
||||
|
||||
/// Parse Podman container status from exited boolean
|
||||
static ContainerStatus fromPodmanExited(bool? exited) {
|
||||
if (exited == true) return ContainerStatus.exited;
|
||||
if (exited == false) return ContainerStatus.running;
|
||||
return ContainerStatus.unknown;
|
||||
}
|
||||
|
||||
/// Get display string for the status
|
||||
String get displayName {
|
||||
return switch (this) {
|
||||
ContainerStatus.running => l10n.running,
|
||||
ContainerStatus.exited => libL10n.exit,
|
||||
ContainerStatus.created => 'Created',
|
||||
ContainerStatus.paused => 'Paused',
|
||||
ContainerStatus.restarting => 'Restarting',
|
||||
ContainerStatus.removing => 'Removing',
|
||||
ContainerStatus.dead => 'Dead',
|
||||
ContainerStatus.unknown => libL10n.unknown,
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -188,7 +188,7 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
|
||||
Widget _buildPs(ContainerState containerState) {
|
||||
final items = containerState.items;
|
||||
if (items == null) return UIs.placeholder;
|
||||
final running = items.where((e) => e.running).length;
|
||||
final running = items.where((e) => e.status.isRunning).length;
|
||||
final stopped = items.length - running;
|
||||
final subtitle = stopped > 0
|
||||
? l10n.dockerStatusRunningAndStoppedFmt(running, stopped)
|
||||
@@ -219,8 +219,8 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
|
||||
),
|
||||
Text(
|
||||
'${item.image ?? l10n.unknown} - ${switch (item) {
|
||||
final PodmanPs ps => ps.running ? l10n.running : l10n.stopped,
|
||||
final DockerPs ps => ps.state,
|
||||
final PodmanPs ps => ps.status.displayName,
|
||||
final DockerPs ps => ps.state ?? ps.status.displayName,
|
||||
}}',
|
||||
style: UIs.text13Grey,
|
||||
),
|
||||
@@ -277,7 +277,7 @@ class _ContainerPageState extends ConsumerState<ContainerPage> {
|
||||
|
||||
Widget _buildMoreBtn(ContainerPs dItem) {
|
||||
return PopupMenu(
|
||||
items: ContainerMenu.items(dItem.running).map((e) => PopMenu.build(e, e.icon, e.toStr)).toList(),
|
||||
items: ContainerMenu.items(dItem.status.isRunning).map((e) => PopMenu.build(e, e.icon, e.toStr)).toList(),
|
||||
onSelected: (item) => _onTapMoreBtn(item, dItem),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:server_box/data/model/container/ps.dart';
|
||||
import 'package:server_box/data/model/container/status.dart';
|
||||
|
||||
void main() {
|
||||
test('docker ps parse', () {
|
||||
@@ -26,7 +27,92 @@ fa1215b4be74 Up 12 hours firefly
|
||||
expect(ps.names, names[idx - 1]);
|
||||
expect(ps.image, images[idx - 1]);
|
||||
expect(ps.state, states[idx - 1]);
|
||||
expect(ps.running, true);
|
||||
expect(ps.status, ContainerStatus.running);
|
||||
expect(ps.status.isRunning, true);
|
||||
}
|
||||
});
|
||||
|
||||
test('docker ps status detection', () {
|
||||
// Test various Docker container states
|
||||
final testCases = [
|
||||
// Running states
|
||||
{'state': 'Up 2 minutes', 'status': ContainerStatus.running},
|
||||
{'state': 'Up 1 hour', 'status': ContainerStatus.running},
|
||||
{'state': 'UP 30 seconds', 'status': ContainerStatus.running}, // Case insensitive
|
||||
{'state': 'up 5 days', 'status': ContainerStatus.running}, // Case insensitive
|
||||
|
||||
// Non-running states
|
||||
{'state': 'Exited (0) 5 minutes ago', 'status': ContainerStatus.exited},
|
||||
{'state': 'Created', 'status': ContainerStatus.created},
|
||||
{'state': 'Paused', 'status': ContainerStatus.paused},
|
||||
{'state': 'Restarting', 'status': ContainerStatus.restarting},
|
||||
{'state': 'Removing', 'status': ContainerStatus.removing},
|
||||
{'state': 'Dead', 'status': ContainerStatus.dead},
|
||||
|
||||
// Edge cases
|
||||
{'state': null, 'status': ContainerStatus.unknown},
|
||||
{'state': '', 'status': ContainerStatus.unknown},
|
||||
{'state': 'Some Unknown Status', 'status': ContainerStatus.unknown},
|
||||
];
|
||||
|
||||
for (final testCase in testCases) {
|
||||
final ps = DockerPs(id: 'test', state: testCase['state'] as String?);
|
||||
final expectedStatus = testCase['status'] as ContainerStatus;
|
||||
expect(
|
||||
ps.status,
|
||||
expectedStatus,
|
||||
reason: 'State "${testCase['state']}" should be ${expectedStatus.name}'
|
||||
);
|
||||
|
||||
// Test status.isRunning method
|
||||
expect(
|
||||
ps.status.isRunning,
|
||||
expectedStatus.isRunning,
|
||||
reason: 'State "${testCase['state']}" isRunning should match status.isRunning'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('podman ps status detection', () {
|
||||
final testCases = [
|
||||
{'exited': false, 'status': ContainerStatus.running},
|
||||
{'exited': true, 'status': ContainerStatus.exited},
|
||||
{'exited': null, 'status': ContainerStatus.unknown},
|
||||
];
|
||||
|
||||
for (final testCase in testCases) {
|
||||
final ps = PodmanPs(id: 'test', exited: testCase['exited'] as bool?);
|
||||
final expectedStatus = testCase['status'] as ContainerStatus;
|
||||
expect(
|
||||
ps.status,
|
||||
expectedStatus,
|
||||
reason: 'Exited "${testCase['exited']}" should be ${expectedStatus.name}'
|
||||
);
|
||||
|
||||
// Test status.isRunning method
|
||||
expect(
|
||||
ps.status.isRunning,
|
||||
expectedStatus.isRunning,
|
||||
reason: 'Exited "${testCase['exited']}" isRunning should match status.isRunning'
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
test('container status utility methods', () {
|
||||
expect(ContainerStatus.running.isRunning, true);
|
||||
expect(ContainerStatus.exited.isRunning, false);
|
||||
expect(ContainerStatus.created.isRunning, false);
|
||||
|
||||
expect(ContainerStatus.exited.canStart, true);
|
||||
expect(ContainerStatus.created.canStart, true);
|
||||
expect(ContainerStatus.running.canStart, false);
|
||||
|
||||
expect(ContainerStatus.running.canStop, true);
|
||||
expect(ContainerStatus.paused.canStop, true);
|
||||
expect(ContainerStatus.exited.canStop, false);
|
||||
|
||||
expect(ContainerStatus.running.canRestart, true);
|
||||
expect(ContainerStatus.removing.canRestart, false);
|
||||
expect(ContainerStatus.unknown.canRestart, false);
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user