Network map enhancements and fixes
This commit is contained in:
@@ -1,17 +1,12 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../../models/network/network_device.dart';
|
||||
import 'network_device_icon.dart';
|
||||
|
||||
/// A styled node representing one network device on the topology canvas.
|
||||
///
|
||||
/// Designed to feel solid at ~120-160dp wide so it remains legible at the
|
||||
/// "good for 200 nodes" zoom level called out in the V1 spec.
|
||||
///
|
||||
/// The node has three visual states beyond its default rest:
|
||||
/// - [isSelected]: stronger primary-tinted background + 2dp primary border.
|
||||
/// - [isHighlighted]: softer secondary tint + primary outline, used when an
|
||||
/// edge touching this device is hovered on the canvas.
|
||||
/// - [isSelected] takes precedence over [isHighlighted].
|
||||
/// Accepts an explicit [nodeSize] so role-based sizing can be applied by the
|
||||
/// canvas without this widget needing to know the layout strategy.
|
||||
class DeviceNode extends StatelessWidget {
|
||||
const DeviceNode({
|
||||
super.key,
|
||||
@@ -19,6 +14,7 @@ class DeviceNode extends StatelessWidget {
|
||||
this.portCount,
|
||||
this.isSelected = false,
|
||||
this.isHighlighted = false,
|
||||
this.nodeSize = const Size(160, 110),
|
||||
this.onTap,
|
||||
});
|
||||
|
||||
@@ -26,29 +22,9 @@ class DeviceNode extends StatelessWidget {
|
||||
final int? portCount;
|
||||
final bool isSelected;
|
||||
final bool isHighlighted;
|
||||
final Size nodeSize;
|
||||
final VoidCallback? onTap;
|
||||
|
||||
IconData _iconForKind(NetworkDeviceKind kind) {
|
||||
switch (kind) {
|
||||
case NetworkDeviceKind.router:
|
||||
return Icons.router_outlined;
|
||||
case NetworkDeviceKind.switchDevice:
|
||||
return Icons.hub_outlined;
|
||||
case NetworkDeviceKind.ap:
|
||||
return Icons.wifi_outlined;
|
||||
case NetworkDeviceKind.firewall:
|
||||
return Icons.security_outlined;
|
||||
case NetworkDeviceKind.server:
|
||||
return Icons.dns_outlined;
|
||||
case NetworkDeviceKind.endpoint:
|
||||
return Icons.devices_outlined;
|
||||
case NetworkDeviceKind.patchPanel:
|
||||
return Icons.view_module_outlined;
|
||||
case NetworkDeviceKind.other:
|
||||
return Icons.device_unknown_outlined;
|
||||
}
|
||||
}
|
||||
|
||||
Color _accentForRole(ColorScheme cs, NetworkDeviceRole? role) {
|
||||
switch (role) {
|
||||
case NetworkDeviceRole.core:
|
||||
@@ -65,11 +41,25 @@ class DeviceNode extends StatelessWidget {
|
||||
}
|
||||
}
|
||||
|
||||
Color? _statusColor(ColorScheme cs, NetworkDeviceStatus status) {
|
||||
switch (status) {
|
||||
case NetworkDeviceStatus.online:
|
||||
return const Color(0xFF34A853); // Google green — universally understood
|
||||
case NetworkDeviceStatus.offline:
|
||||
return cs.error;
|
||||
case NetworkDeviceStatus.warning:
|
||||
return const Color(0xFFFBBC04); // Amber
|
||||
case NetworkDeviceStatus.unknown:
|
||||
return null; // No dot shown
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final cs = Theme.of(context).colorScheme;
|
||||
final tt = Theme.of(context).textTheme;
|
||||
final accent = _accentForRole(cs, device.role);
|
||||
final statusColor = _statusColor(cs, device.status);
|
||||
|
||||
final bgColor = isSelected
|
||||
? cs.primaryContainer
|
||||
@@ -105,67 +95,94 @@ class DeviceNode extends StatelessWidget {
|
||||
child: InkWell(
|
||||
onTap: onTap,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
width: 160,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Row(
|
||||
child: Stack(
|
||||
children: [
|
||||
Container(
|
||||
width: nodeSize.width,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(_iconForKind(device.kind), size: 18, color: accent),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
device.kind.label,
|
||||
style: tt.labelSmall?.copyWith(color: accent),
|
||||
Row(
|
||||
children: [
|
||||
NetworkDeviceIcon(
|
||||
kind: device.kind,
|
||||
color: accent,
|
||||
size: 20,
|
||||
),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
device.kind.label,
|
||||
style: tt.labelSmall?.copyWith(color: accent),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
device.name,
|
||||
style: tt.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (device.vendor != null || device.model != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
[
|
||||
if (device.vendor != null) device.vendor,
|
||||
if (device.model != null) device.model,
|
||||
].whereType<String>().join(' • '),
|
||||
style: tt.labelSmall?.copyWith(
|
||||
color: cs.onSurfaceVariant),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
device.name,
|
||||
style: tt.bodyMedium?.copyWith(fontWeight: FontWeight.w600),
|
||||
maxLines: 2,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
if (device.vendor != null || device.model != null) ...[
|
||||
const SizedBox(height: 2),
|
||||
Text(
|
||||
[
|
||||
if (device.vendor != null) device.vendor,
|
||||
if (device.model != null) device.model,
|
||||
].whereType<String>().join(' • '),
|
||||
style: tt.labelSmall?.copyWith(color: cs.onSurfaceVariant),
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
if (portCount != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cable_outlined,
|
||||
size: 12,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'$portCount ports',
|
||||
style: tt.labelSmall?.copyWith(
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
if (portCount != null) ...[
|
||||
const SizedBox(height: 4),
|
||||
Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.cable_outlined,
|
||||
size: 12,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'$portCount ports',
|
||||
style: tt.labelSmall?.copyWith(
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
// Status dot — top-right, only shown when status is not unknown
|
||||
if (statusColor != null)
|
||||
Positioned(
|
||||
top: 8,
|
||||
right: 8,
|
||||
child: Container(
|
||||
width: 8,
|
||||
height: 8,
|
||||
decoration: BoxDecoration(
|
||||
color: statusColor,
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: bgColor,
|
||||
width: 1.5,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
Reference in New Issue
Block a user