iop/apps/client/lib/main.dart

217 lines
5.8 KiB
Dart

import 'dart:async';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:iop_console/iop_console.dart';
import 'client_config.dart';
import 'iop_wire/client_wire_client.dart';
import 'src/integrations/mattermost/mattermost_push_host_integration.dart';
import 'src/integrations/mattermost/mattermost_push_plugin_client.dart';
final _mattermostHost = MattermostPushHostIntegration(
pushClient: MattermostPushPluginClient(),
);
const iopDefaultCapabilityPack = IopCapabilityPack(
capabilities: [
IopCapability(
name: 'Edge Control',
description: 'Monitor and configure Edge routing groups.',
),
IopCapability(
name: 'Node Management',
description: 'Track node registration and execution lanes.',
),
IopCapability(
name: 'Runtime Dispatch',
description: 'Trigger manual adapter execution sessions.',
),
IopCapability(
name: 'Execution Tracing',
description: 'View live logs and execution metrics stream.',
),
IopCapability(
name: 'Maintenance Mode',
description: 'Trigger node bootstrap or cluster repairs.',
),
],
);
Future<void> main() => runIopClient();
Future<void> applyFullscreenMode() async {
await SystemChrome.setEnabledSystemUIMode(SystemUiMode.immersiveSticky);
SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent,
),
);
}
Future<void> bootstrapIopClient() async {
await applyFullscreenMode();
await Firebase.initializeApp();
await _mattermostHost.initialize();
}
Future<void> runIopClient() async {
WidgetsFlutterBinding.ensureInitialized();
await bootstrapIopClient();
runApp(IopClientApp(mattermostHost: _mattermostHost));
}
class IopClientApp extends StatelessWidget {
final ClientWireClient? testClient;
final MattermostPushHostIntegration? mattermostHost;
const IopClientApp({super.key, this.testClient, this.mattermostHost});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'IOP Client',
debugShowCheckedModeBanner: false,
theme: ThemeData.dark(useMaterial3: true).copyWith(
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFF6366F1),
brightness: Brightness.dark,
primary: const Color(0xFF6366F1),
secondary: const Color(0xFF10B981),
),
scaffoldBackgroundColor: const Color(0xFF0F172A),
),
home: ClientHomePage(
testClient: testClient,
mattermostHost: mattermostHost,
),
);
}
}
class ClientHomePage extends StatefulWidget {
final ClientWireClient? testClient;
final MattermostPushHostIntegration? mattermostHost;
const ClientHomePage({super.key, this.testClient, this.mattermostHost});
@override
State<ClientHomePage> createState() => _ClientHomePageState();
}
class _ClientHomePageState extends State<ClientHomePage> {
String _wireStatus = 'Disconnected';
ClientWireClient? _client;
StreamSubscription? _notificationSubscription;
@override
void initState() {
super.initState();
// Auto-connect on start
WidgetsBinding.instance.addPostFrameCallback((_) {
applyFullscreenMode();
_connectWire();
});
final host = widget.mattermostHost;
if (host != null) {
_notificationSubscription = host.onNotification.listen(
_showMattermostNotification,
);
}
}
@override
void dispose() {
_notificationSubscription?.cancel();
_client?.close();
super.dispose();
}
void _showMattermostNotification(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> _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) {
final config = IopConsoleConfig(
controlPlaneHttpUrl: ClientConfig.controlPlaneHttpUrl,
controlPlaneWireUrl: ClientConfig.controlPlaneWireUrl,
);
return IopConsoleShell(
config: config,
capabilities: iopDefaultCapabilityPack,
overview: IopConsoleOverview(
config: config,
statusText: _wireStatus,
onRefresh: _connectWire,
),
agent: const IopAgentPanel(capabilities: iopDefaultCapabilityPack),
);
}
}