Compare commits

...
3 Commits
Author SHA1 Message Date
Jeroen SchweitzerandClaude Opus 4.5 2e1d1dd457 refactor: reorganize Control Room navigation
Build and Push / release (push) Successful in 2s
Build and Push / build (push) Successful in 3m0s
- Consolidate Containers and Proxy Hosts under "Stack" section
- Add "Data Management" section with database browser placeholders:
  PostgreSQL, Redis, Qdrant, Neo4j
- Remove unused nav items: Networks, Volumes, Images (Portainer),
  Redirections, Streams, Certificates (NPM)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 20:34:25 +01:00
Jeroen SchweitzerandClaude Opus 4.5 34fdc77818 fix: prevent API client provider auto-dispose causing Ref invalidation
Build and Push / release (push) Successful in 4s
Build and Push / build (push) Successful in 2m58s
API client providers (coreApiClientProvider, tatlockApiClientProvider) now
use keepAlive: true. This fixes "DioException [unknown]: null" errors on
pages like /security/users where ref.read() was used without subscription.

The AuthInterceptor stores a Ref that became invalid when the provider
auto-disposed after a one-time read.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 20:08:46 +01:00
Jeroen SchweitzerandClaude Opus 4.5 c374ecbffb feat: add visible version debugPrint on app startup
🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-04 19:36:52 +01:00
6 changed files with 74 additions and 63 deletions
+15
View File
@@ -7,6 +7,21 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
## [1.1.12] - 2026-01-04
### Changed
- Control Room navigation reorganized:
- New "Stack" section with Containers and Proxy Hosts
- New "Data Management" section with PostgreSQL, Redis, Qdrant, Neo4j placeholders
- Removed: Networks, Volumes, Images (Portainer) and Redirections, Streams, Certificates (NPM)
## [1.1.11] - 2026-01-04
### Fixed
- API client providers now use `keepAlive: true` to prevent Ref invalidation
- Fixes "DioException [unknown]: null" error on /security/users and other API pages
- AuthInterceptor's stored Ref was becoming invalid when provider auto-disposed
## [1.1.10] - 2026-01-04 ## [1.1.10] - 2026-01-04
### Changed ### Changed
+8 -2
View File
@@ -9,7 +9,10 @@ import 'api_client_native.dart' if (dart.library.html) 'api_client_web.dart'
part 'api_client.g.dart'; part 'api_client.g.dart';
/// Provides the Dio instance for Core API. /// Provides the Dio instance for Core API.
@riverpod ///
/// Uses keepAlive to prevent auto-dispose - the AuthInterceptor stores
/// a Ref that must remain valid for the lifetime of API requests.
@Riverpod(keepAlive: true)
Dio coreApiClient(Ref ref) { Dio coreApiClient(Ref ref) {
final options = BaseOptions( final options = BaseOptions(
baseUrl: AppConfig.coreApiUrl, baseUrl: AppConfig.coreApiUrl,
@@ -33,7 +36,10 @@ Dio coreApiClient(Ref ref) {
} }
/// Provides the Dio instance for Tatlock API. /// Provides the Dio instance for Tatlock API.
@riverpod ///
/// Uses keepAlive to prevent auto-dispose - the AuthInterceptor stores
/// a Ref that must remain valid for the lifetime of API requests.
@Riverpod(keepAlive: true)
Dio tatlockApiClient(Ref ref) { Dio tatlockApiClient(Ref ref) {
final options = BaseOptions( final options = BaseOptions(
baseUrl: AppConfig.tatlockApiUrl, baseUrl: AppConfig.tatlockApiUrl,
@@ -69,44 +69,11 @@ class _SectionContent extends ConsumerWidget {
@override @override
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
switch (nav) { return switch (nav) {
case ControlRoomNav.containers: ControlRoomNav.containers => const _ContainersSection(),
return const _ContainersSection(); ControlRoomNav.proxyHosts => const ProxyHostsPage(),
case ControlRoomNav.proxyHosts: _ => _PlaceholderSection(nav: nav),
return const ProxyHostsPage(); };
default:
return _PlaceholderSection(nav: nav);
}
}
}
/// Containers section with optional stack filter.
class _ContainersSection extends ConsumerWidget {
const _ContainersSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedStack = ref.watch(selectedStackProvider);
final colorScheme = Theme.of(context).colorScheme;
return Row(
children: [
// Stacks filter panel
const _StacksFilterPanel(),
// Divider
VerticalDivider(
width: 1,
thickness: 1,
color: colorScheme.outlineVariant,
),
// Main content - containers list or stack detail
Expanded(
child: selectedStack == null
? const ContainersListPage()
: StackDetailPage(stackId: selectedStack),
),
],
);
} }
} }
@@ -150,6 +117,36 @@ class _PlaceholderSection extends StatelessWidget {
} }
} }
/// Containers section with optional stack filter.
class _ContainersSection extends ConsumerWidget {
const _ContainersSection();
@override
Widget build(BuildContext context, WidgetRef ref) {
final selectedStack = ref.watch(selectedStackProvider);
final colorScheme = Theme.of(context).colorScheme;
return Row(
children: [
// Stacks filter panel
const _StacksFilterPanel(),
// Divider
VerticalDivider(
width: 1,
thickness: 1,
color: colorScheme.outlineVariant,
),
// Main content - containers list or stack detail
Expanded(
child: selectedStack == null
? const ContainersListPage()
: StackDetailPage(stackId: selectedStack),
),
],
);
}
}
/// Stacks filter panel for Containers section (includes "All Containers" option). /// Stacks filter panel for Containers section (includes "All Containers" option).
class _StacksFilterPanel extends ConsumerWidget { class _StacksFilterPanel extends ConsumerWidget {
const _StacksFilterPanel(); const _StacksFilterPanel();
+14 -18
View File
@@ -7,30 +7,26 @@ import 'package:tatlock_ui/shared/layouts/widgets/nav_panel.dart';
/// Route paths for Control Room. /// Route paths for Control Room.
abstract class ControlRoomRoutes { abstract class ControlRoomRoutes {
static const base = '/control-room'; static const base = '/control-room';
// Portainer // Stack
static const containers = '/control-room/containers'; static const containers = '/control-room/containers';
static const networks = '/control-room/networks';
static const volumes = '/control-room/volumes';
static const images = '/control-room/images';
// NPM
static const proxyHosts = '/control-room/proxy-hosts'; static const proxyHosts = '/control-room/proxy-hosts';
static const redirections = '/control-room/redirections'; // Data Management
static const streams = '/control-room/streams'; static const postgres = '/control-room/postgres';
static const certificates = '/control-room/certificates'; static const redis = '/control-room/redis';
static const qdrant = '/control-room/qdrant';
static const neo4j = '/control-room/neo4j';
} }
/// Control Room navigation items with section grouping. /// Control Room navigation items with section grouping.
enum ControlRoomNav { enum ControlRoomNav {
// Portainer section // Stack section - Docker containers and reverse proxy
containers('containers', 'Containers', Icons.dns, 'Portainer'), containers('containers', 'Containers', Icons.dns, 'Stack'),
networks('networks', 'Networks', Icons.hub, 'Portainer'), proxyHosts('proxy-hosts', 'Proxy Hosts', Icons.public, 'Stack'),
volumes('volumes', 'Volumes', Icons.storage, 'Portainer'), // Data Management section - Database browsers
images('images', 'Images', Icons.photo_library, 'Portainer'), postgres('postgres', 'PostgreSQL', Icons.table_chart, 'Data Management'),
// NPM section redis('redis', 'Redis', Icons.memory, 'Data Management'),
proxyHosts('proxy-hosts', 'Proxy Hosts', Icons.public, 'NPM'), qdrant('qdrant', 'Qdrant', Icons.scatter_plot, 'Data Management'),
redirections('redirections', 'Redirections', Icons.alt_route, 'NPM'), neo4j('neo4j', 'Neo4j', Icons.hub, 'Data Management');
streams('streams', 'Streams', Icons.stream, 'NPM'),
certificates('certificates', 'SSL Certificates', Icons.verified_user, 'NPM');
const ControlRoomNav(this.id, this.label, this.icon, this.section); const ControlRoomNav(this.id, this.label, this.icon, this.section);
+1 -4
View File
@@ -14,10 +14,7 @@ void main() async {
// Use path-based URLs on web (no-op on mobile/desktop) // Use path-based URLs on web (no-op on mobile/desktop)
configureUrlStrategy(); configureUrlStrategy();
developer.log( debugPrint('🪣 ${AppVersion.name} v${AppVersion.fullVersion}');
'${AppVersion.name} v${AppVersion.fullVersion}',
name: 'tatlock_ui',
);
// Initialize auth before starting the app. // Initialize auth before starting the app.
// This handles OIDC callback and silent auth on web. // This handles OIDC callback and silent auth on web.
+1 -1
View File
@@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 1.1.10+1 version: 1.1.12+1
environment: environment:
sdk: ^3.10.4 sdk: ^3.10.4