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 ports, /// Environment variables (key-value pairs). @Default({}) Map environment, /// Container labels. @Default({}) Map labels, /// Mounted volumes. @Default([]) List mounts, /// Networks the container is connected to. @Default([]) List 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'; }