- Add IopConsoleContract and IopConsoleOverview components - Update iop_console.dart entry point - Update iop_agent_panel with new console integration - Update iop_console_shell for workbench embedding - Add agent task documentation for m-embeddable-iop-console-workbench
323 lines
9.1 KiB
Dart
323 lines
9.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
|
|
import 'iop_agent_panel.dart';
|
|
import 'iop_console_contract.dart';
|
|
|
|
enum IopConsoleSection {
|
|
overview,
|
|
edges,
|
|
nodes,
|
|
runtime,
|
|
executionLogs,
|
|
maintenance,
|
|
agent,
|
|
settings,
|
|
}
|
|
|
|
class IopConsoleShell extends StatefulWidget {
|
|
final Widget overview;
|
|
final Widget? edges;
|
|
final Widget? nodes;
|
|
final Widget? runtime;
|
|
final Widget? executionLogs;
|
|
final Widget? maintenance;
|
|
final Widget? agent;
|
|
final Widget? settings;
|
|
final IopConsoleSection initialSection;
|
|
|
|
// Injected Contracts
|
|
final IopConsoleConfig? config;
|
|
final IopConsoleThemeAdapter? themeAdapter;
|
|
final IopConsoleNavigationCallback? onNavigate;
|
|
final IopCapabilityPack? capabilities;
|
|
|
|
const IopConsoleShell({
|
|
super.key,
|
|
required this.overview,
|
|
this.edges,
|
|
this.nodes,
|
|
this.runtime,
|
|
this.executionLogs,
|
|
this.maintenance,
|
|
this.agent,
|
|
this.settings,
|
|
this.initialSection = IopConsoleSection.overview,
|
|
this.config,
|
|
this.themeAdapter,
|
|
this.onNavigate,
|
|
this.capabilities,
|
|
});
|
|
|
|
@override
|
|
State<IopConsoleShell> createState() => _IopConsoleShellState();
|
|
}
|
|
|
|
class _IopConsoleShellState extends State<IopConsoleShell> {
|
|
late IopConsoleSection _section = widget.initialSection;
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final theme = widget.themeAdapter ?? const IopConsoleThemeAdapter();
|
|
return Scaffold(
|
|
backgroundColor: theme.backgroundColor,
|
|
body: SafeArea(
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
_IopConsoleRail(
|
|
selected: _section,
|
|
onSelect: _select,
|
|
theme: theme,
|
|
),
|
|
Expanded(child: _buildContent()),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _select(IopConsoleSection section) {
|
|
setState(() {
|
|
_section = section;
|
|
});
|
|
widget.onNavigate?.call(section);
|
|
}
|
|
|
|
Widget _buildContent() {
|
|
final theme = widget.themeAdapter ?? const IopConsoleThemeAdapter();
|
|
return switch (_section) {
|
|
IopConsoleSection.overview => widget.overview,
|
|
IopConsoleSection.edges =>
|
|
widget.edges ??
|
|
_IopPlaceholder(
|
|
icon: Icons.device_hub_outlined,
|
|
title: 'Edges',
|
|
subtitle: 'Edge group status and command surfaces mount here.',
|
|
theme: theme,
|
|
),
|
|
IopConsoleSection.nodes =>
|
|
widget.nodes ??
|
|
_IopPlaceholder(
|
|
icon: Icons.memory_outlined,
|
|
title: 'Nodes',
|
|
subtitle:
|
|
'Node health, runtime inventory, and execution lanes mount here.',
|
|
theme: theme,
|
|
),
|
|
IopConsoleSection.runtime =>
|
|
widget.runtime ??
|
|
_IopPlaceholder(
|
|
icon: Icons.speed_outlined,
|
|
title: 'Runtime',
|
|
subtitle:
|
|
'Adapter, target, profile, and routing controls mount here.',
|
|
theme: theme,
|
|
),
|
|
IopConsoleSection.executionLogs =>
|
|
widget.executionLogs ??
|
|
_IopPlaceholder(
|
|
icon: Icons.list_alt_outlined,
|
|
title: 'Execution & Logs',
|
|
subtitle:
|
|
'Live session logs, active traces, and execution timelines mount here.',
|
|
theme: theme,
|
|
),
|
|
IopConsoleSection.maintenance =>
|
|
widget.maintenance ??
|
|
_IopPlaceholder(
|
|
icon: Icons.build_circle_outlined,
|
|
title: 'Maintenance',
|
|
subtitle:
|
|
'Field diagnostics, bootstrap, and repair commands mount here.',
|
|
theme: theme,
|
|
),
|
|
IopConsoleSection.agent =>
|
|
widget.agent ?? IopAgentPanel(capabilities: widget.capabilities),
|
|
IopConsoleSection.settings =>
|
|
widget.settings ??
|
|
_IopPlaceholder(
|
|
icon: Icons.tune,
|
|
title: 'Settings',
|
|
subtitle:
|
|
'Control Plane endpoint and operator preferences mount here.',
|
|
theme: theme,
|
|
),
|
|
};
|
|
}
|
|
}
|
|
|
|
class _IopConsoleRail extends StatelessWidget {
|
|
final IopConsoleSection selected;
|
|
final ValueChanged<IopConsoleSection> onSelect;
|
|
final IopConsoleThemeAdapter theme;
|
|
|
|
const _IopConsoleRail({
|
|
required this.selected,
|
|
required this.onSelect,
|
|
required this.theme,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
width: 60,
|
|
decoration: BoxDecoration(
|
|
color: theme.railBackgroundColor,
|
|
border: Border(right: BorderSide(color: theme.borderColor)),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
const SizedBox(height: 8),
|
|
_RailButton(
|
|
tooltip: 'Overview',
|
|
icon: Icons.dashboard_outlined,
|
|
selected: selected == IopConsoleSection.overview,
|
|
onPressed: () => onSelect(IopConsoleSection.overview),
|
|
theme: theme,
|
|
),
|
|
_RailButton(
|
|
tooltip: 'Edges',
|
|
icon: Icons.device_hub_outlined,
|
|
selected: selected == IopConsoleSection.edges,
|
|
onPressed: () => onSelect(IopConsoleSection.edges),
|
|
theme: theme,
|
|
),
|
|
_RailButton(
|
|
tooltip: 'Nodes',
|
|
icon: Icons.memory_outlined,
|
|
selected: selected == IopConsoleSection.nodes,
|
|
onPressed: () => onSelect(IopConsoleSection.nodes),
|
|
theme: theme,
|
|
),
|
|
_RailButton(
|
|
tooltip: 'Runtime',
|
|
icon: Icons.speed_outlined,
|
|
selected: selected == IopConsoleSection.runtime,
|
|
onPressed: () => onSelect(IopConsoleSection.runtime),
|
|
theme: theme,
|
|
),
|
|
_RailButton(
|
|
tooltip: 'Execution & Logs',
|
|
icon: Icons.list_alt_outlined,
|
|
selected: selected == IopConsoleSection.executionLogs,
|
|
onPressed: () => onSelect(IopConsoleSection.executionLogs),
|
|
theme: theme,
|
|
),
|
|
_RailButton(
|
|
tooltip: 'Maintenance',
|
|
icon: Icons.build_circle_outlined,
|
|
selected: selected == IopConsoleSection.maintenance,
|
|
onPressed: () => onSelect(IopConsoleSection.maintenance),
|
|
theme: theme,
|
|
),
|
|
const Spacer(),
|
|
_RailButton(
|
|
tooltip: 'Agent',
|
|
icon: Icons.smart_toy_outlined,
|
|
selected: selected == IopConsoleSection.agent,
|
|
onPressed: () => onSelect(IopConsoleSection.agent),
|
|
theme: theme,
|
|
),
|
|
_RailButton(
|
|
tooltip: 'Settings',
|
|
icon: Icons.tune,
|
|
selected: selected == IopConsoleSection.settings,
|
|
onPressed: () => onSelect(IopConsoleSection.settings),
|
|
theme: theme,
|
|
),
|
|
const SizedBox(height: 8),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RailButton extends StatelessWidget {
|
|
final String tooltip;
|
|
final IconData icon;
|
|
final bool selected;
|
|
final VoidCallback onPressed;
|
|
final IopConsoleThemeAdapter theme;
|
|
|
|
const _RailButton({
|
|
required this.tooltip,
|
|
required this.icon,
|
|
required this.selected,
|
|
required this.onPressed,
|
|
required this.theme,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final color = selected ? theme.primaryColor : theme.textSecondaryColor;
|
|
return Tooltip(
|
|
message: tooltip,
|
|
waitDuration: const Duration(milliseconds: 500),
|
|
child: SizedBox(
|
|
width: 56,
|
|
height: 48,
|
|
child: IconButton(
|
|
onPressed: onPressed,
|
|
icon: Icon(icon, color: color),
|
|
style: IconButton.styleFrom(
|
|
backgroundColor: selected
|
|
? theme.primaryColor.withValues(alpha: 0.1)
|
|
: Colors.transparent,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(8),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _IopPlaceholder extends StatelessWidget {
|
|
final IconData icon;
|
|
final String title;
|
|
final String subtitle;
|
|
final IopConsoleThemeAdapter theme;
|
|
|
|
const _IopPlaceholder({
|
|
required this.icon,
|
|
required this.title,
|
|
required this.subtitle,
|
|
required this.theme,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Container(
|
|
color: theme.backgroundColor,
|
|
child: Center(
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 500),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(icon, color: theme.primaryColor, size: 40),
|
|
const SizedBox(height: 16),
|
|
Text(
|
|
title,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
color: theme.textColor,
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
subtitle,
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(color: theme.textSecondaryColor, fontSize: 14),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|