217 lines
5.8 KiB
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),
|
|
);
|
|
}
|
|
|
|
}
|