Fixed Edit Port Dialog on Network Map

This commit is contained in:
2026-06-06 18:30:58 +08:00
parent 65a42039ee
commit e49b52949c
4 changed files with 41 additions and 29 deletions
@@ -655,8 +655,8 @@ class _PortsList extends ConsumerWidget {
PortListTile( PortListTile(
port: p, port: p,
canEdit: true, canEdit: true,
onTap: () => showPortEditDialog( onTap: (ctx) => showPortEditDialog(
context: context, context: ctx,
ref: ref, ref: ref,
deviceId: deviceId, deviceId: deviceId,
existing: p, existing: p,
@@ -132,8 +132,8 @@ class NetworkMapDeviceScreen extends ConsumerWidget {
linkedPortLabel: otherPortLabel(port.id), linkedPortLabel: otherPortLabel(port.id),
canEdit: canEdit, canEdit: canEdit,
onTap: canEdit onTap: canEdit
? () => showPortEditDialog( ? (ctx) => showPortEditDialog(
context: context, context: ctx,
ref: ref, ref: ref,
deviceId: device.id, deviceId: device.id,
existing: port, existing: port,
@@ -131,18 +131,19 @@ class _PortEditDialogState extends ConsumerState<_PortEditDialog> {
return AlertDialog( return AlertDialog(
title: Text(widget.existing == null ? 'Add port' : 'Edit port'), title: Text(widget.existing == null ? 'Add port' : 'Edit port'),
content: ConstrainedBox( content: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 420), constraints: const BoxConstraints(minWidth: 320, maxWidth: 480),
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.stretch, crossAxisAlignment: CrossAxisAlignment.stretch,
children: [ children: [
// ── Identity ─────────────────────────────────────────
TextField( TextField(
controller: _numberCtrl, controller: _numberCtrl,
autofocus: widget.existing == null, autofocus: widget.existing == null,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Port number *', labelText: 'Port number *',
helperText: 'Exactly as labeled on the device (e.g. Gi0/1, eth0, 24)', helperText: 'e.g. Gi0/1, eth0, port24',
), ),
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -161,6 +162,7 @@ class _PortEditDialogState extends ConsumerState<_PortEditDialog> {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Expanded( Expanded(
child: TextField( child: TextField(
@@ -182,39 +184,43 @@ class _PortEditDialogState extends ConsumerState<_PortEditDialog> {
inputFormatters: [FilteringTextInputFormatter.digitsOnly], inputFormatters: [FilteringTextInputFormatter.digitsOnly],
decoration: InputDecoration( decoration: InputDecoration(
labelText: 'Access VLAN', labelText: 'Access VLAN',
helperText: _isTrunk helperText: _isTrunk ? 'Disabled for trunk' : '14094',
? 'Disabled — trunk port'
: 'Single VLAN ID (14094)',
), ),
), ),
), ),
], ],
), ),
const SizedBox(height: 12), // ── Switching config ──────────────────────────────────
const Divider(height: 28),
SwitchListTile.adaptive( SwitchListTile.adaptive(
value: _isTrunk, value: _isTrunk,
onChanged: (v) => setState(() => _isTrunk = v), onChanged: (v) => setState(() => _isTrunk = v),
contentPadding: EdgeInsets.zero, contentPadding: EdgeInsets.zero,
title: const Text('Trunk port'), title: const Text('Trunk port'),
subtitle: Text( subtitle: Text('Carries multiple VLANs', style: tt.bodySmall),
'Carries multiple VLANs',
style: tt.bodySmall,
), ),
), AnimatedSize(
if (_isTrunk) ...[ duration: const Duration(milliseconds: 200),
const SizedBox(height: 4), curve: Curves.easeInOut,
TextField( child: _isTrunk
? Padding(
padding: const EdgeInsets.only(top: 8),
child: TextField(
controller: _trunkVlansCtrl, controller: _trunkVlansCtrl,
decoration: const InputDecoration( decoration: const InputDecoration(
labelText: 'Allowed VLANs (comma-separated)', labelText: 'Allowed VLANs',
helperText: 'e.g. 10, 20, 100-105', helperText: 'Comma-separated, e.g. 10, 20, 100',
), ),
), ),
], )
: const SizedBox.shrink(),
),
// ── Notes ─────────────────────────────────────────────
const SizedBox(height: 12), const SizedBox(height: 12),
TextField( TextField(
controller: _notesCtrl, controller: _notesCtrl,
maxLines: 2, minLines: 2,
maxLines: 4,
decoration: const InputDecoration(labelText: 'Notes'), decoration: const InputDecoration(labelText: 'Notes'),
), ),
], ],
@@ -228,7 +234,13 @@ class _PortEditDialogState extends ConsumerState<_PortEditDialog> {
), ),
FilledButton( FilledButton(
onPressed: _saving ? null : _save, onPressed: _saving ? null : _save,
child: Text(_saving ? 'Saving…' : 'Save'), child: _saving
? const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: Text(widget.existing == null ? 'Add' : 'Save'),
), ),
], ],
); );
@@ -21,7 +21,7 @@ class PortListTile extends StatelessWidget {
final String? linkedDeviceName; final String? linkedDeviceName;
final String? linkedPortLabel; final String? linkedPortLabel;
final bool canEdit; final bool canEdit;
final VoidCallback? onTap; final void Function(BuildContext)? onTap;
final void Function(BuildContext)? onConnect; final void Function(BuildContext)? onConnect;
final void Function(BuildContext)? onDisconnect; final void Function(BuildContext)? onDisconnect;
final VoidCallback? onDelete; final VoidCallback? onDelete;
@@ -40,7 +40,7 @@ class PortListTile extends StatelessWidget {
]; ];
return ListTile( return ListTile(
onTap: onTap, onTap: onTap == null ? null : () => onTap!(context),
leading: CircleAvatar( leading: CircleAvatar(
backgroundColor: isLinked backgroundColor: isLinked
? cs.primaryContainer ? cs.primaryContainer
@@ -75,7 +75,7 @@ class PortListTile extends StatelessWidget {
onSelected: (action) { onSelected: (action) {
switch (action) { switch (action) {
case _PortAction.editPort: case _PortAction.editPort:
onTap?.call(); onTap?.call(context);
case _PortAction.connect: case _PortAction.connect:
onConnect?.call(context); onConnect?.call(context);
case _PortAction.disconnect: case _PortAction.disconnect: