- Move fleet polling logic to control orchestrator - Split client state management into separate controller/repository - Add client bootstrap and home page components - Update HTTP fleet handlers and views - Add unit tests for client bootstrap and status controller - Archive completed subtask documents
161 lines
4.5 KiB
Dart
161 lines
4.5 KiB
Dart
import 'package:flutter/foundation.dart';
|
|
import 'control_plane_status_client.dart';
|
|
|
|
class ControlPlaneStatusController extends ChangeNotifier {
|
|
final ControlPlaneStatusRepository statusRepository;
|
|
|
|
List<FleetEdgeView> _edges = [];
|
|
bool _loadingEdges = false;
|
|
String? _edgesError;
|
|
String? _selectedEdgeId;
|
|
|
|
final Map<String, EdgeStatusResponseView> _edgeStatuses = {};
|
|
final Map<String, bool> _loadingStatuses = {};
|
|
final Map<String, String?> _statusErrors = {};
|
|
|
|
final Map<String, List<EdgeNodeEventView>> _edgeEvents = {};
|
|
final Map<String, bool> _loadingEvents = {};
|
|
final Map<String, String?> _eventErrors = {};
|
|
|
|
bool _disposed = false;
|
|
|
|
ControlPlaneStatusController({required this.statusRepository});
|
|
|
|
// Getters
|
|
List<FleetEdgeView> get edges => _edges;
|
|
bool get loadingEdges => _loadingEdges;
|
|
String? get edgesError => _edgesError;
|
|
String? get selectedEdgeId => _selectedEdgeId;
|
|
|
|
Map<String, EdgeStatusResponseView> get edgeStatuses => _edgeStatuses;
|
|
Map<String, bool> get loadingStatuses => _loadingStatuses;
|
|
Map<String, String?> get statusErrors => _statusErrors;
|
|
|
|
Map<String, List<EdgeNodeEventView>> get edgeEvents => _edgeEvents;
|
|
Map<String, bool> get loadingEvents => _loadingEvents;
|
|
Map<String, String?> get eventErrors => _eventErrors;
|
|
|
|
@override
|
|
void dispose() {
|
|
_disposed = true;
|
|
super.dispose();
|
|
}
|
|
|
|
void _notifyIfActive() {
|
|
if (!_disposed) {
|
|
notifyListeners();
|
|
}
|
|
}
|
|
|
|
void selectEdge(String edgeId) {
|
|
if (_disposed) return;
|
|
if (_selectedEdgeId == edgeId) return;
|
|
_selectedEdgeId = edgeId;
|
|
_notifyIfActive();
|
|
fetchEdgeDetails(edgeId);
|
|
}
|
|
|
|
Future<void> fetchEdges() async {
|
|
if (_disposed) return;
|
|
_loadingEdges = true;
|
|
_edgesError = null;
|
|
_notifyIfActive();
|
|
|
|
try {
|
|
final fleetResponse = await statusRepository.fetchFleetStatus();
|
|
if (_disposed) return;
|
|
_edges = fleetResponse.edges;
|
|
_loadingEdges = false;
|
|
|
|
// Normalize selection: check if the previously selected edge is still present
|
|
final hasSelected = _edges.any((e) => e.edgeId == _selectedEdgeId);
|
|
if (!hasSelected) {
|
|
// Clear stale status/events/loading/error cache to prevent leak/inconsistency
|
|
if (_selectedEdgeId != null) {
|
|
_edgeStatuses.remove(_selectedEdgeId);
|
|
_loadingStatuses.remove(_selectedEdgeId);
|
|
_statusErrors.remove(_selectedEdgeId);
|
|
_edgeEvents.remove(_selectedEdgeId);
|
|
_loadingEvents.remove(_selectedEdgeId);
|
|
_eventErrors.remove(_selectedEdgeId);
|
|
}
|
|
|
|
if (_edges.isNotEmpty) {
|
|
_selectedEdgeId = _edges.first.edgeId;
|
|
} else {
|
|
_selectedEdgeId = null;
|
|
}
|
|
}
|
|
_notifyIfActive();
|
|
|
|
if (_selectedEdgeId != null) {
|
|
await fetchEdgeDetails(_selectedEdgeId!);
|
|
}
|
|
} catch (e) {
|
|
if (_disposed) return;
|
|
_edgesError = e.toString();
|
|
_loadingEdges = false;
|
|
_notifyIfActive();
|
|
}
|
|
}
|
|
|
|
Future<void> fetchEdgeDetails(String edgeId) async {
|
|
if (_disposed) return;
|
|
await Future.wait([fetchEdgeStatus(edgeId), fetchEdgeEvents(edgeId)]);
|
|
if (_disposed) return;
|
|
}
|
|
|
|
Future<void> fetchEdgeStatus(String edgeId) async {
|
|
if (_disposed) return;
|
|
_loadingStatuses[edgeId] = true;
|
|
_statusErrors[edgeId] = null;
|
|
_notifyIfActive();
|
|
|
|
try {
|
|
final status = await statusRepository.fetchEdgeStatus(edgeId);
|
|
if (_disposed) return;
|
|
_edgeStatuses[edgeId] = status;
|
|
_loadingStatuses[edgeId] = false;
|
|
_notifyIfActive();
|
|
} catch (e) {
|
|
if (_disposed) return;
|
|
_statusErrors[edgeId] = e.toString();
|
|
_loadingStatuses[edgeId] = false;
|
|
_notifyIfActive();
|
|
}
|
|
}
|
|
|
|
Future<void> fetchEdgeEvents(String edgeId) async {
|
|
if (_disposed) return;
|
|
_loadingEvents[edgeId] = true;
|
|
_eventErrors[edgeId] = null;
|
|
_notifyIfActive();
|
|
|
|
try {
|
|
final events = await statusRepository.fetchEdgeEvents(edgeId);
|
|
if (_disposed) return;
|
|
_edgeEvents[edgeId] = events;
|
|
_loadingEvents[edgeId] = false;
|
|
_notifyIfActive();
|
|
} catch (e) {
|
|
if (_disposed) return;
|
|
_eventErrors[edgeId] = e.toString();
|
|
_loadingEvents[edgeId] = false;
|
|
_notifyIfActive();
|
|
}
|
|
}
|
|
|
|
Future<void> refreshSelectedStatus() async {
|
|
if (_disposed) return;
|
|
if (_selectedEdgeId != null) {
|
|
await fetchEdgeStatus(_selectedEdgeId!);
|
|
}
|
|
}
|
|
|
|
Future<void> refreshSelectedEvents() async {
|
|
if (_disposed) return;
|
|
if (_selectedEdgeId != null) {
|
|
await fetchEdgeEvents(_selectedEdgeId!);
|
|
}
|
|
}
|
|
}
|