feat: add Control Room with containers and stacks management

- 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>
This commit is contained in:
Jeroen Schweitzer
2025-12-31 02:01:01 +01:00
co-authored by Claude Opus 4.5
parent dd6bcbdbda
commit 3b89ed8c18
42 changed files with 4166 additions and 21 deletions
@@ -0,0 +1,62 @@
import 'package:freezed_annotation/freezed_annotation.dart';
import '../../domain/entities/stack.dart';
part 'stack_model.freezed.dart';
part 'stack_model.g.dart';
/// Stack data model for API serialization.
@freezed
class StackModel with _$StackModel {
const factory StackModel({
required String id,
required String name,
@JsonKey(name: 'type') String? typeString,
@JsonKey(name: 'status') String? statusString,
@JsonKey(name: 'container_count') @Default(0) int containerCount,
@JsonKey(name: 'running_count') @Default(0) int runningCount,
@JsonKey(name: 'compose_file') String? composeFile,
String? environment,
@JsonKey(name: 'created_at') String? createdAt,
@JsonKey(name: 'updated_at') String? updatedAt,
}) = _StackModel;
const StackModel._();
factory StackModel.fromJson(Map<String, dynamic> json) =>
_$StackModelFromJson(json);
/// Converts to domain entity.
Stack toEntity() {
return Stack(
id: id,
name: name,
type: _parseStackType(typeString),
status: _parseStackStatus(statusString),
containerCount: containerCount,
runningCount: runningCount,
composeFile: composeFile,
environment: environment,
createdAt: createdAt != null ? DateTime.tryParse(createdAt!) : null,
updatedAt: updatedAt != null ? DateTime.tryParse(updatedAt!) : null,
);
}
StackType _parseStackType(String? type) {
return switch (type?.toLowerCase()) {
'compose' => StackType.compose,
'swarm' => StackType.swarm,
'kubernetes' || 'k8s' => StackType.kubernetes,
_ => StackType.compose,
};
}
StackStatus _parseStackStatus(String? status) {
return switch (status?.toLowerCase()) {
'active' || 'running' => StackStatus.active,
'inactive' || 'stopped' => StackStatus.inactive,
'error' => StackStatus.error,
_ => StackStatus.unknown,
};
}
}