Network map enhancements and fixes
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../../../models/network/network_link.dart';
|
||||
import '../../../models/network/network_port.dart';
|
||||
import '../../../providers/network_map/network_devices_provider.dart';
|
||||
|
||||
/// Opens an M3 dialog to connect [portId] to a port on another device.
|
||||
///
|
||||
/// Returns true if a link was successfully created.
|
||||
Future<bool> showLinkEditDialog({
|
||||
required BuildContext context,
|
||||
required WidgetRef ref,
|
||||
required String portId,
|
||||
required String currentDeviceId,
|
||||
}) async {
|
||||
final result = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (_) => _LinkEditDialog(
|
||||
portId: portId,
|
||||
currentDeviceId: currentDeviceId,
|
||||
),
|
||||
);
|
||||
return result == true;
|
||||
}
|
||||
|
||||
class _LinkEditDialog extends ConsumerStatefulWidget {
|
||||
const _LinkEditDialog({
|
||||
required this.portId,
|
||||
required this.currentDeviceId,
|
||||
});
|
||||
|
||||
final String portId;
|
||||
final String currentDeviceId;
|
||||
|
||||
@override
|
||||
ConsumerState<_LinkEditDialog> createState() => _LinkEditDialogState();
|
||||
}
|
||||
|
||||
class _LinkEditDialogState extends ConsumerState<_LinkEditDialog> {
|
||||
String? _selectedDeviceId;
|
||||
String? _selectedPortId;
|
||||
NetworkLinkKind? _linkKind;
|
||||
final _cableLabelCtrl = TextEditingController();
|
||||
bool _saving = false;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_cableLabelCtrl.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _save() async {
|
||||
final portA = widget.portId;
|
||||
final portB = _selectedPortId;
|
||||
if (portB == null) return;
|
||||
setState(() => _saving = true);
|
||||
try {
|
||||
ref.invalidate(networkLinksProvider);
|
||||
final controller = ref.read(networkDevicesControllerProvider);
|
||||
await controller.createLink(
|
||||
portA: portA,
|
||||
portB: portB,
|
||||
linkKind: _linkKind,
|
||||
cableLabel: _cableLabelCtrl.text.trim().isEmpty
|
||||
? null
|
||||
: _cableLabelCtrl.text.trim(),
|
||||
);
|
||||
if (mounted) Navigator.of(context).pop(true);
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Failed to create link: $e'),
|
||||
backgroundColor: Theme.of(context).colorScheme.error,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _saving = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final devicesAsync = ref.watch(networkDevicesProvider);
|
||||
final portsAsync = _selectedDeviceId != null
|
||||
? ref.watch(networkPortsByDeviceProvider(_selectedDeviceId!))
|
||||
: const AsyncValue<List<NetworkPort>>.data([]);
|
||||
final allLinksAsync = ref.watch(networkLinksProvider);
|
||||
|
||||
final otherDevices = (devicesAsync.valueOrNull ?? const [])
|
||||
.where((d) => d.id != widget.currentDeviceId)
|
||||
.toList()
|
||||
..sort((a, b) => a.name.compareTo(b.name));
|
||||
|
||||
final allLinks = allLinksAsync.valueOrNull ?? const [];
|
||||
final usedPortIds = <String>{
|
||||
for (final link in allLinks) ...[link.portA, link.portB],
|
||||
};
|
||||
|
||||
final devicePorts = portsAsync.valueOrNull ?? const [];
|
||||
final availablePorts =
|
||||
devicePorts.where((p) => !usedPortIds.contains(p.id)).toList();
|
||||
|
||||
final canSave = _selectedPortId != null && !_saving;
|
||||
|
||||
return AlertDialog(
|
||||
title: const Text('Connect port'),
|
||||
content: ConstrainedBox(
|
||||
constraints: const BoxConstraints(maxWidth: 420),
|
||||
child: SingleChildScrollView(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: [
|
||||
DropdownButtonFormField<String>(
|
||||
initialValue: _selectedDeviceId,
|
||||
decoration:
|
||||
const InputDecoration(labelText: 'Remote device *'),
|
||||
items: [
|
||||
for (final d in otherDevices)
|
||||
DropdownMenuItem(value: d.id, child: Text(d.name)),
|
||||
],
|
||||
onChanged: (v) => setState(() {
|
||||
_selectedDeviceId = v;
|
||||
_selectedPortId = null;
|
||||
}),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<String>(
|
||||
key: ValueKey(_selectedDeviceId),
|
||||
initialValue: _selectedPortId,
|
||||
decoration: InputDecoration(
|
||||
labelText: 'Remote port *',
|
||||
helperText: _selectedDeviceId == null
|
||||
? 'Select a device first'
|
||||
: availablePorts.isEmpty
|
||||
? 'No available ports on this device'
|
||||
: null,
|
||||
),
|
||||
items: [
|
||||
for (final p in availablePorts)
|
||||
DropdownMenuItem(
|
||||
value: p.id,
|
||||
child: Text(p.portNumber),
|
||||
),
|
||||
],
|
||||
onChanged: _selectedDeviceId == null
|
||||
? null
|
||||
: (v) => setState(() => _selectedPortId = v),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
DropdownButtonFormField<NetworkLinkKind?>(
|
||||
initialValue: _linkKind,
|
||||
decoration: const InputDecoration(labelText: 'Link type'),
|
||||
items: [
|
||||
const DropdownMenuItem<NetworkLinkKind?>(
|
||||
value: null,
|
||||
child: Text('(unknown)'),
|
||||
),
|
||||
for (final k in NetworkLinkKind.values)
|
||||
DropdownMenuItem(value: k, child: Text(k.label)),
|
||||
],
|
||||
onChanged: (v) => setState(() => _linkKind = v),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
TextField(
|
||||
controller: _cableLabelCtrl,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Cable label',
|
||||
helperText: 'e.g. "CAT6-01", "SMF-A3" (optional)',
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: [
|
||||
TextButton(
|
||||
onPressed: _saving ? null : () => Navigator.pop(context, false),
|
||||
child: const Text('Cancel'),
|
||||
),
|
||||
FilledButton(
|
||||
onPressed: canSave ? _save : null,
|
||||
child: Text(_saving ? 'Saving…' : 'Connect'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user