chore: release v0.3.3
Build and Push / build (release) Successful in 3m6s

Features:
- Health check endpoint for Portainer monitoring
- Local search filtering in DataGrid
- Container status badges reflect health (green/orange/blue)

Improvements:
- Standardized 56px header heights across panels
- Container grid parses Docker API format correctly
- Search bar styling improvements
- Status badges have consistent width

Fixes:
- Quick links persistence (link type, form refresh)
- Iframe switching closes existing content first
- ContainerState type conflict resolved

Branding:
- Updated favicon and icons with Tatlock bucket logo

🤖 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
2026-01-03 16:12:18 +01:00
co-authored by Claude Opus 4.5
parent 0d15d3dbb5
commit bdced8739c
26 changed files with 499 additions and 136 deletions
@@ -1,48 +1,67 @@
import 'package:flutter/material.dart' hide Container;
import 'package:tatlock_ui/features/control_room/containers/domain/entities/container.dart';
import 'package:tatlock_ui/features/control_room/containers/presentation/pages/containers_list_page.dart';
/// Container state enum for status display.
enum ContainerState {
created,
running,
paused,
restarting,
removing,
exited,
dead;
/// Status badge for container state with optional health indication.
class ContainerStatusBadge extends StatelessWidget {
const ContainerStatusBadge({
super.key,
required this.state,
this.health,
this.showLabel = true,
});
/// Parse a string to ContainerState.
static ContainerState fromString(String value) {
/// Create badge from a string state value.
factory ContainerStatusBadge.fromString(
String state, {
ContainerHealth? health,
bool showLabel = true,
}) {
return ContainerStatusBadge(
state: _parseState(state),
health: health,
showLabel: showLabel,
);
}
/// Create badge from state enum and status string (parses health from status).
factory ContainerStatusBadge.withStatus({
required ContainerState state,
required String status,
bool showLabel = true,
}) {
return ContainerStatusBadge(
state: state,
health: _parseHealthFromStatus(status),
showLabel: showLabel,
);
}
/// Parse health status from Docker status string.
static ContainerHealth _parseHealthFromStatus(String status) {
final lower = status.toLowerCase();
if (lower.contains('(healthy)')) return ContainerHealth.healthy;
if (lower.contains('(unhealthy)')) return ContainerHealth.unhealthy;
if (lower.contains('(health: starting)')) return ContainerHealth.starting;
return ContainerHealth.none;
}
final ContainerState state;
final ContainerHealth? health;
final bool showLabel;
static ContainerState _parseState(String value) {
return ContainerState.values.firstWhere(
(s) => s.name == value.toLowerCase(),
orElse: () => ContainerState.exited,
);
}
}
/// Status badge for container state.
class ContainerStatusBadge extends StatelessWidget {
const ContainerStatusBadge({
super.key,
required this.state,
this.showLabel = true,
});
/// Create badge from a string state value.
factory ContainerStatusBadge.fromString(String state, {bool showLabel = true}) {
return ContainerStatusBadge(
state: ContainerState.fromString(state),
showLabel: showLabel,
);
}
final ContainerState state;
final bool showLabel;
@override
Widget build(BuildContext context) {
final (color, icon, label) = _getStateStyle(context);
return DecoratedBox(
Widget badge = DecoratedBox(
decoration: BoxDecoration(
color: color.withValues(alpha: 0.15),
borderRadius: BorderRadius.circular(4),
@@ -69,17 +88,22 @@ class ContainerStatusBadge extends StatelessWidget {
),
),
);
if (showLabel) {
badge = ConstrainedBox(
constraints: const BoxConstraints(minWidth: 90),
child: badge,
);
}
return badge;
}
(Color, IconData, String) _getStateStyle(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
return switch (state) {
ContainerState.running => (
Colors.green,
Icons.play_circle,
'Running',
),
ContainerState.running => _getRunningStyle(colorScheme),
ContainerState.paused => (
Colors.orange,
Icons.pause_circle,
@@ -112,4 +136,30 @@ class ContainerStatusBadge extends StatelessWidget {
),
};
}
/// Get style for running state, factoring in health status.
(Color, IconData, String) _getRunningStyle(ColorScheme colorScheme) {
return switch (health) {
ContainerHealth.healthy => (
Colors.green,
Icons.play_circle,
'Running',
),
ContainerHealth.unhealthy => (
Colors.orange,
Icons.warning_amber_rounded,
'Unhealthy',
),
ContainerHealth.starting => (
Colors.blue,
Icons.hourglass_top,
'Starting',
),
ContainerHealth.none || null => (
Colors.green,
Icons.play_circle,
'Running',
),
};
}
}