- Add Control Room page with stacks sidebar and containers list - Implement container actions (start/stop/restart) with snackbars - Add container logs viewer dialog - Add search/filter for both stacks and containers lists - Add external links for Portainer and Netdata (url_launcher) - Add VS Code launch configuration for Flutter web debugging 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
172 lines
4.0 KiB
Dart
172 lines
4.0 KiB
Dart
import 'package:freezed_annotation/freezed_annotation.dart';
|
|
|
|
part 'container.freezed.dart';
|
|
|
|
/// Docker container entity.
|
|
@freezed
|
|
class Container with _$Container {
|
|
const factory Container({
|
|
/// Container ID (short form).
|
|
required String id,
|
|
|
|
/// Full container ID.
|
|
required String fullId,
|
|
|
|
/// Container name (without leading slash).
|
|
required String name,
|
|
|
|
/// Image name with tag.
|
|
required String image,
|
|
|
|
/// Current container state.
|
|
required ContainerState state,
|
|
|
|
/// Container status string (e.g., "Up 2 hours").
|
|
required String status,
|
|
|
|
/// Stack/project this container belongs to.
|
|
String? stackName,
|
|
|
|
/// Stack ID if part of a stack.
|
|
String? stackId,
|
|
|
|
/// Mapped ports.
|
|
@Default([]) List<PortMapping> ports,
|
|
|
|
/// Environment variables (key-value pairs).
|
|
@Default({}) Map<String, String> environment,
|
|
|
|
/// Container labels.
|
|
@Default({}) Map<String, String> labels,
|
|
|
|
/// Mounted volumes.
|
|
@Default([]) List<VolumeMount> mounts,
|
|
|
|
/// Networks the container is connected to.
|
|
@Default([]) List<String> networks,
|
|
|
|
/// When the container was created.
|
|
DateTime? createdAt,
|
|
|
|
/// When the container was started.
|
|
DateTime? startedAt,
|
|
|
|
/// CPU usage percentage (0-100).
|
|
double? cpuPercent,
|
|
|
|
/// Memory usage in bytes.
|
|
int? memoryUsage,
|
|
|
|
/// Memory limit in bytes.
|
|
int? memoryLimit,
|
|
}) = _Container;
|
|
|
|
const Container._();
|
|
|
|
/// Whether the container is running.
|
|
bool get isRunning => state == ContainerState.running;
|
|
|
|
/// Whether the container can be started.
|
|
bool get canStart =>
|
|
state == ContainerState.exited ||
|
|
state == ContainerState.created ||
|
|
state == ContainerState.paused;
|
|
|
|
/// Whether the container can be stopped.
|
|
bool get canStop => state == ContainerState.running;
|
|
|
|
/// Whether the container can be restarted.
|
|
bool get canRestart =>
|
|
state == ContainerState.running || state == ContainerState.exited;
|
|
|
|
/// Memory usage as a percentage of the limit.
|
|
double? get memoryPercent {
|
|
if (memoryUsage == null || memoryLimit == null || memoryLimit == 0) {
|
|
return null;
|
|
}
|
|
return (memoryUsage! / memoryLimit!) * 100;
|
|
}
|
|
|
|
/// Formatted memory usage string.
|
|
String get memoryFormatted {
|
|
if (memoryUsage == null) return '--';
|
|
return _formatBytes(memoryUsage!);
|
|
}
|
|
|
|
/// Formatted memory limit string.
|
|
String get memoryLimitFormatted {
|
|
if (memoryLimit == null) return '--';
|
|
return _formatBytes(memoryLimit!);
|
|
}
|
|
|
|
String _formatBytes(int bytes) {
|
|
if (bytes < 1024) return '$bytes B';
|
|
if (bytes < 1024 * 1024) return '${(bytes / 1024).toStringAsFixed(1)} KB';
|
|
if (bytes < 1024 * 1024 * 1024) {
|
|
return '${(bytes / (1024 * 1024)).toStringAsFixed(1)} MB';
|
|
}
|
|
return '${(bytes / (1024 * 1024 * 1024)).toStringAsFixed(2)} GB';
|
|
}
|
|
}
|
|
|
|
/// Container state.
|
|
enum ContainerState {
|
|
created,
|
|
running,
|
|
paused,
|
|
restarting,
|
|
removing,
|
|
exited,
|
|
dead,
|
|
}
|
|
|
|
/// Port mapping configuration.
|
|
@freezed
|
|
class PortMapping with _$PortMapping {
|
|
const factory PortMapping({
|
|
/// Host IP (usually 0.0.0.0).
|
|
String? hostIp,
|
|
|
|
/// Port on the host.
|
|
int? hostPort,
|
|
|
|
/// Port inside the container.
|
|
required int containerPort,
|
|
|
|
/// Protocol (tcp/udp).
|
|
@Default('tcp') String protocol,
|
|
}) = _PortMapping;
|
|
|
|
const PortMapping._();
|
|
|
|
/// Formatted string representation.
|
|
String get formatted {
|
|
if (hostPort == null) return '$containerPort/$protocol';
|
|
final ip = hostIp == '0.0.0.0' ? '' : '$hostIp:';
|
|
return '$ip$hostPort->$containerPort/$protocol';
|
|
}
|
|
}
|
|
|
|
/// Volume mount configuration.
|
|
@freezed
|
|
class VolumeMount with _$VolumeMount {
|
|
const factory VolumeMount({
|
|
/// Mount type (bind, volume, tmpfs).
|
|
required String type,
|
|
|
|
/// Source path or volume name.
|
|
required String source,
|
|
|
|
/// Destination path in container.
|
|
required String destination,
|
|
|
|
/// Mount mode (rw, ro).
|
|
@Default('rw') String mode,
|
|
}) = _VolumeMount;
|
|
|
|
const VolumeMount._();
|
|
|
|
/// Whether the mount is read-only.
|
|
bool get isReadOnly => mode == 'ro';
|
|
}
|