217 lines
6.4 KiB
Dart
217 lines
6.4 KiB
Dart
import 'dart:async';
|
|
|
|
import 'package:flutter/material.dart';
|
|
import 'package:iop_console/iop_console.dart';
|
|
|
|
import 'client_config.dart';
|
|
import 'control_plane_status_client.dart';
|
|
import 'control_plane_status_controller.dart';
|
|
import 'control_plane_status_widgets.dart';
|
|
import 'iop_wire/client_wire_client.dart';
|
|
import 'main.dart';
|
|
import 'src/integrations/nexo/nexo_notification_host_integration.dart';
|
|
|
|
class ClientHomePage extends StatefulWidget {
|
|
final ClientWireClient? testClient;
|
|
final NexoNotificationHostIntegration? notificationHost;
|
|
final ControlPlaneStatusRepository? statusRepository;
|
|
|
|
const ClientHomePage({
|
|
super.key,
|
|
this.testClient,
|
|
this.notificationHost,
|
|
this.statusRepository,
|
|
});
|
|
|
|
@override
|
|
State<ClientHomePage> createState() => _ClientHomePageState();
|
|
}
|
|
|
|
class _ClientHomePageState extends State<ClientHomePage> {
|
|
String _wireStatus = 'Disconnected';
|
|
ClientWireClient? _client;
|
|
StreamSubscription? _notificationSubscription;
|
|
HttpControlPlaneStatusRepository? _ownedStatusRepository;
|
|
|
|
late final ControlPlaneStatusController _controller;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
final ownedStatusRepository = widget.statusRepository == null
|
|
? HttpControlPlaneStatusRepository(
|
|
baseUrl: ClientConfig.controlPlaneHttpUrl,
|
|
)
|
|
: null;
|
|
_ownedStatusRepository = ownedStatusRepository;
|
|
final statusRepo = widget.statusRepository ?? ownedStatusRepository!;
|
|
_controller = ControlPlaneStatusController(statusRepository: statusRepo);
|
|
|
|
// Auto-connect on start
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
applyFullscreenMode();
|
|
_connectAndFetch();
|
|
});
|
|
|
|
final host = widget.notificationHost;
|
|
if (host != null) {
|
|
_notificationSubscription = host.onNotification.listen(
|
|
_showNexoNotification,
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_notificationSubscription?.cancel();
|
|
_client?.close();
|
|
_controller.dispose();
|
|
_ownedStatusRepository?.close();
|
|
super.dispose();
|
|
}
|
|
|
|
void _showNexoNotification(Map<String, dynamic> data) {
|
|
if (data['type'] != 'message' || !mounted) return;
|
|
|
|
final message = data['message'] as String? ?? '';
|
|
final channel = data['channel_name'] as String? ?? '';
|
|
final sender = data['sender_name'] as String? ?? '';
|
|
final content = sender.isNotEmpty
|
|
? '[$channel] $sender: $message'
|
|
: message;
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(content, maxLines: 2, overflow: TextOverflow.ellipsis),
|
|
duration: const Duration(seconds: 4),
|
|
behavior: SnackBarBehavior.floating,
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _connectAndFetch() async {
|
|
await Future.wait([_connectWire(), _controller.fetchEdges()]);
|
|
}
|
|
|
|
Future<void> _connectWire() async {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_wireStatus = 'Connecting';
|
|
});
|
|
|
|
try {
|
|
ClientWireClient client;
|
|
if (widget.testClient != null) {
|
|
client = widget.testClient!;
|
|
} else {
|
|
client = await ClientWireClient.connectToUrl(
|
|
ClientConfig.controlPlaneWireUrl,
|
|
);
|
|
}
|
|
_client = client;
|
|
|
|
// Hello handshake
|
|
final response = await client.hello(
|
|
clientId: 'client-ui',
|
|
clientVersion: '1.0.0',
|
|
);
|
|
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_wireStatus = response.ready ? 'Connected' : 'Error';
|
|
});
|
|
|
|
client.addDisconnectListener((_) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_wireStatus = 'Disconnected';
|
|
_client = null;
|
|
});
|
|
});
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
setState(() {
|
|
_wireStatus = 'Error';
|
|
_client = null;
|
|
});
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return ListenableBuilder(
|
|
listenable: _controller,
|
|
builder: (context, _) {
|
|
final config = IopConsoleConfig(
|
|
controlPlaneHttpUrl: ClientConfig.controlPlaneHttpUrl,
|
|
controlPlaneWireUrl: ClientConfig.controlPlaneWireUrl,
|
|
);
|
|
|
|
final currentEdgeId = _controller.selectedEdgeId;
|
|
|
|
return IopConsoleShell(
|
|
config: config,
|
|
capabilities: iopDefaultCapabilityPack,
|
|
overview: IopConsoleOverview(
|
|
config: config,
|
|
statusText: _wireStatus,
|
|
onRefresh: _connectAndFetch,
|
|
),
|
|
edges: EdgesPanel(
|
|
edges: _controller.edges,
|
|
isLoading: _controller.loadingEdges,
|
|
error: _controller.edgesError,
|
|
selectedEdgeId: currentEdgeId,
|
|
onSelectEdge: _controller.selectEdge,
|
|
onRefresh: _controller.fetchEdges,
|
|
),
|
|
nodes: NodesPanel(
|
|
edges: _controller.edges,
|
|
selectedEdgeId: currentEdgeId,
|
|
selectedEdgeStatus: currentEdgeId != null
|
|
? _controller.edgeStatuses[currentEdgeId]
|
|
: null,
|
|
isLoading: currentEdgeId != null
|
|
? (_controller.loadingStatuses[currentEdgeId] ?? false)
|
|
: false,
|
|
error: currentEdgeId != null
|
|
? _controller.statusErrors[currentEdgeId]
|
|
: null,
|
|
onSelectEdge: _controller.selectEdge,
|
|
onRefresh: () {
|
|
if (currentEdgeId != null) {
|
|
_controller.fetchEdgeStatus(currentEdgeId);
|
|
}
|
|
},
|
|
),
|
|
runtime: RuntimePanel(
|
|
edges: _controller.edges,
|
|
selectedEdgeId: currentEdgeId,
|
|
onSelectEdge: _controller.selectEdge,
|
|
statusRepository: _controller.statusRepository,
|
|
),
|
|
executionLogs: ExecutionLogsPanel(
|
|
edges: _controller.edges,
|
|
selectedEdgeId: currentEdgeId,
|
|
events: currentEdgeId != null
|
|
? (_controller.edgeEvents[currentEdgeId] ?? [])
|
|
: [],
|
|
isLoading: currentEdgeId != null
|
|
? (_controller.loadingEvents[currentEdgeId] ?? false)
|
|
: false,
|
|
error: currentEdgeId != null
|
|
? _controller.eventErrors[currentEdgeId]
|
|
: null,
|
|
onSelectEdge: _controller.selectEdge,
|
|
onRefresh: () {
|
|
if (currentEdgeId != null) {
|
|
_controller.fetchEdgeEvents(currentEdgeId);
|
|
}
|
|
},
|
|
),
|
|
agent: const IopAgentPanel(capabilities: iopDefaultCapabilityPack),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
}
|