iop/apps/client/lib/control_plane_status_controller.dart
toki 9024c852df refactor: architecture refactor - fleet polling & client state split
- 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
2026-06-06 19:10:47 +09:00

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!);
}
}
}