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:
co-authored by
Claude Opus 4.5
parent
dd6bcbdbda
commit
3b89ed8c18
@@ -0,0 +1,130 @@
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../data_grid_source.dart';
|
||||
|
||||
/// Data source adapter for Core API endpoints.
|
||||
///
|
||||
/// Implements the DataGrid data source interface for fetching data
|
||||
/// from the Core API with support for search, sorting, and pagination.
|
||||
///
|
||||
/// Example:
|
||||
/// ```dart
|
||||
/// final containersSource = CoreApiDataSource<Container>(
|
||||
/// dio: dio,
|
||||
/// endpoint: '/infrastructure/containers',
|
||||
/// fromJson: Container.fromJson,
|
||||
/// searchParam: 'search',
|
||||
/// sortParam: 'sort_by',
|
||||
/// orderParam: 'order',
|
||||
/// );
|
||||
/// ```
|
||||
class CoreApiDataSource<T> extends DataGridSource<T> {
|
||||
CoreApiDataSource({
|
||||
required this.dio,
|
||||
required this.endpoint,
|
||||
required this.fromJson,
|
||||
this.searchParam = 'search',
|
||||
this.sortParam = 'sort',
|
||||
this.orderParam = 'order',
|
||||
this.offsetParam = 'offset',
|
||||
this.limitParam = 'limit',
|
||||
this.itemsKey = 'items',
|
||||
this.totalCountKey = 'total',
|
||||
});
|
||||
|
||||
/// Dio HTTP client instance.
|
||||
final Dio dio;
|
||||
|
||||
/// API endpoint path.
|
||||
final String endpoint;
|
||||
|
||||
/// Function to parse JSON into the item type.
|
||||
final T Function(Map<String, dynamic> json) fromJson;
|
||||
|
||||
/// Query parameter name for search.
|
||||
final String searchParam;
|
||||
|
||||
/// Query parameter name for sort field.
|
||||
final String sortParam;
|
||||
|
||||
/// Query parameter name for sort order.
|
||||
final String orderParam;
|
||||
|
||||
/// Query parameter name for pagination offset.
|
||||
final String offsetParam;
|
||||
|
||||
/// Query parameter name for pagination limit.
|
||||
final String limitParam;
|
||||
|
||||
/// JSON key for items array in response.
|
||||
final String itemsKey;
|
||||
|
||||
/// JSON key for total count in response.
|
||||
final String totalCountKey;
|
||||
|
||||
@override
|
||||
Future<DataGridResult<T>> fetch({
|
||||
String? searchQuery,
|
||||
String? sortField,
|
||||
bool sortDescending = false,
|
||||
int? offset,
|
||||
int? limit,
|
||||
}) async {
|
||||
final queryParams = <String, dynamic>{};
|
||||
|
||||
if (searchQuery != null && searchQuery.isNotEmpty) {
|
||||
queryParams[searchParam] = searchQuery;
|
||||
}
|
||||
|
||||
if (sortField != null) {
|
||||
queryParams[sortParam] = sortField;
|
||||
queryParams[orderParam] = sortDescending ? 'desc' : 'asc';
|
||||
}
|
||||
|
||||
if (offset != null) {
|
||||
queryParams[offsetParam] = offset;
|
||||
}
|
||||
|
||||
if (limit != null) {
|
||||
queryParams[limitParam] = limit;
|
||||
}
|
||||
|
||||
final response = await dio.get<Map<String, dynamic>>(
|
||||
endpoint,
|
||||
queryParameters: queryParams,
|
||||
);
|
||||
|
||||
final data = response.data!;
|
||||
|
||||
// Handle both paginated and wrapped responses
|
||||
List<dynamic> itemsJson;
|
||||
int totalCount;
|
||||
|
||||
if (data.containsKey(itemsKey)) {
|
||||
// Paginated response: { items: [...], total: N }
|
||||
itemsJson = data[itemsKey] as List<dynamic>;
|
||||
totalCount = data[totalCountKey] as int? ?? itemsJson.length;
|
||||
} else {
|
||||
// Try common wrapper patterns: { data: [...] } or { results: [...] }
|
||||
itemsJson = (data['data'] ?? data['results'] ?? []) as List<dynamic>;
|
||||
totalCount = data['count'] as int? ??
|
||||
data['total'] as int? ??
|
||||
data['totalCount'] as int? ??
|
||||
itemsJson.length;
|
||||
}
|
||||
|
||||
final items = itemsJson
|
||||
.map((json) => fromJson(json as Map<String, dynamic>))
|
||||
.toList();
|
||||
|
||||
final hasMore = offset != null && limit != null
|
||||
? (offset + items.length) < totalCount
|
||||
: false;
|
||||
|
||||
return DataGridResult(
|
||||
items: items,
|
||||
totalCount: totalCount,
|
||||
hasMore: hasMore,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user