iop/packages/flutter/iop_console/lib/src/iop_console_shell.dart
toki 173af5729c feat: embeddable iop console workbench - m-embeddable-iop-console-workbench task implementation
- 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
2026-06-02 11:12:36 +09:00

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),
),
],
),
),
),
);
}
}