- Upgrade flutter_riverpod to 3.1.0, riverpod_annotation to 4.0.0 - Upgrade freezed to 3.2.3, freezed_annotation to 3.1.0 - Migrate freezed classes to use sealed keyword (freezed 3.x) - Update provider naming (*NotifierProvider → *Provider) - Add legacy.dart import for StateNotifierProvider compatibility - Fix valueOrNull → value for AsyncValue - Remove unused imports and fields - Add sync from Authentik button to users/groups pages - Suppress invalid_annotation_target warning in analysis_options 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
133 lines
4.4 KiB
Dart
133 lines
4.4 KiB
Dart
import 'package:dio/dio.dart';
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
|
import 'package:tatlock_ui/core/api/api_client.dart';
|
|
import 'package:tatlock_ui/features/control_room/containers/data/datasources/containers_datasource.dart';
|
|
import 'package:tatlock_ui/features/control_room/containers/domain/entities/container.dart';
|
|
import 'package:tatlock_ui/features/control_room/stacks/domain/entities/stack.dart';
|
|
|
|
part 'stacks_datasource.g.dart';
|
|
|
|
/// Data source for stack operations.
|
|
///
|
|
/// Stacks are derived from container labels (Docker Compose convention).
|
|
/// The Core API provides endpoints for stack YAML and environment variables.
|
|
class StacksDatasource {
|
|
StacksDatasource(this._dio, this._containersDatasource);
|
|
|
|
final Dio _dio;
|
|
final ContainersDatasource _containersDatasource;
|
|
|
|
/// Gets all stacks by aggregating container data.
|
|
Future<List<Stack>> getStacks() async {
|
|
final containers = await _containersDatasource.getContainers();
|
|
final containerEntities = containers.map((c) => c.toEntity()).toList();
|
|
|
|
// Group containers by stack name
|
|
final stackMap = <String, List<Container>>{};
|
|
|
|
for (final container in containerEntities) {
|
|
final stackName = container.stackName;
|
|
if (stackName != null) {
|
|
stackMap.putIfAbsent(stackName, () => []).add(container);
|
|
}
|
|
}
|
|
|
|
// Convert to Stack entities
|
|
return stackMap.entries.map((entry) {
|
|
final name = entry.key;
|
|
final stackContainers = entry.value;
|
|
final runningCount = stackContainers.where((c) => c.isRunning).length;
|
|
|
|
return Stack(
|
|
id: name, // Use name as ID for compose stacks
|
|
name: name,
|
|
type: StackType.compose,
|
|
status: runningCount == stackContainers.length
|
|
? StackStatus.active
|
|
: runningCount > 0
|
|
? StackStatus.active
|
|
: StackStatus.inactive,
|
|
containerCount: stackContainers.length,
|
|
runningCount: runningCount,
|
|
);
|
|
}).toList()
|
|
..sort((a, b) => a.name.compareTo(b.name));
|
|
}
|
|
|
|
/// Gets a single stack by ID.
|
|
Future<Stack> getStack(String id) async {
|
|
final stacks = await getStacks();
|
|
return stacks.firstWhere(
|
|
(s) => s.id == id,
|
|
orElse: () => throw Exception('Stack not found: $id'),
|
|
);
|
|
}
|
|
|
|
/// Performs an action on all containers in a stack.
|
|
Future<void> stackAction(String stackId, String action) async {
|
|
final containers = await _containersDatasource.getContainers();
|
|
final stackContainers = containers
|
|
.map((c) => c.toEntity())
|
|
.where((c) => c.stackId == stackId)
|
|
.toList();
|
|
|
|
for (final container in stackContainers) {
|
|
await _containersDatasource.containerAction(container.fullId, action);
|
|
}
|
|
}
|
|
|
|
/// Gets the compose YAML for a stack.
|
|
Future<String> getStackYaml(String stackId) async {
|
|
final response = await _dio.get<String>(
|
|
'/infrastructure/stacks/$stackId/compose',
|
|
);
|
|
return response.data ?? '';
|
|
}
|
|
|
|
/// Updates the compose YAML for a stack.
|
|
Future<void> updateStackYaml(String stackId, String yaml) async {
|
|
await _dio.put<void>(
|
|
'/infrastructure/stacks/$stackId/compose',
|
|
data: yaml,
|
|
options: Options(contentType: 'text/yaml'),
|
|
);
|
|
}
|
|
|
|
/// Gets the environment variables for a stack.
|
|
Future<Map<String, String>> getStackEnvVars(String stackId) async {
|
|
final response = await _dio.get<Map<String, dynamic>>(
|
|
'/infrastructure/stacks/$stackId/env',
|
|
);
|
|
return response.data?.map((k, v) => MapEntry(k, v.toString())) ?? {};
|
|
}
|
|
|
|
/// Updates the environment variables for a stack.
|
|
Future<void> updateStackEnvVars(
|
|
String stackId,
|
|
Map<String, String> envVars,
|
|
) async {
|
|
await _dio.put<void>(
|
|
'/infrastructure/stacks/$stackId/env',
|
|
data: envVars,
|
|
);
|
|
}
|
|
|
|
/// Deploys/redeploys a stack with current configuration.
|
|
Future<void> deployStack(String stackId) async {
|
|
await _dio.post<void>('/infrastructure/stacks/$stackId/deploy');
|
|
}
|
|
|
|
/// Rebuilds a stack (pulls fresh images and recreates containers).
|
|
Future<void> rebuildStack(String stackId) async {
|
|
await _dio.post<void>('/infrastructure/stacks/$stackId/rebuild');
|
|
}
|
|
}
|
|
|
|
/// Provides the stacks datasource.
|
|
@riverpod
|
|
StacksDatasource stacksDatasource(Ref ref) {
|
|
final dio = ref.watch(coreApiClientProvider);
|
|
final containersDatasource = ref.watch(containersDatasourceProvider);
|
|
return StacksDatasource(dio, containersDatasource);
|
|
}
|