Report devices seen on last scan in ARP/SNMP status

The ARP and SNMP scanner status endpoints and front-end cards now expose
the number of devices found by the most recent successful scan, following
the existing mDNS device-count pattern. The count persists across the
running/waiting transitions, and for SNMP a failed poll keeps the last
good count rather than overwriting it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
rzuasti
2026-06-02 08:45:45 -04:00
co-authored by Claude Opus 4.8
parent 38158a4ffc
commit d15411a889
11 changed files with 118 additions and 13 deletions
@@ -2,11 +2,15 @@ class ArpScannerStatus {
final bool isRunning;
final double? runningForSeconds;
final double? nextRunInSeconds;
final int? lastScanDevicesSeen;
final double? lastScanSecondsAgo;
const ArpScannerStatus({
required this.isRunning,
this.runningForSeconds,
this.nextRunInSeconds,
this.lastScanDevicesSeen,
this.lastScanSecondsAgo,
});
factory ArpScannerStatus.fromJson(Map<String, dynamic> json) {
@@ -14,6 +18,8 @@ class ArpScannerStatus {
isRunning: json['is_running'] as bool,
runningForSeconds: (json['running_for_seconds'] as num?)?.toDouble(),
nextRunInSeconds: (json['next_run_in_seconds'] as num?)?.toDouble(),
lastScanDevicesSeen: (json['last_scan_devices_seen'] as num?)?.toInt(),
lastScanSecondsAgo: (json['last_scan_seconds_ago'] as num?)?.toDouble(),
);
}
}
@@ -2,11 +2,15 @@ class SnmpScannerStatus {
final bool isRunning;
final double? runningForSeconds;
final double? nextRunInSeconds;
final int? lastScanDevicesSeen;
final double? lastScanSecondsAgo;
const SnmpScannerStatus({
required this.isRunning,
this.runningForSeconds,
this.nextRunInSeconds,
this.lastScanDevicesSeen,
this.lastScanSecondsAgo,
});
factory SnmpScannerStatus.fromJson(Map<String, dynamic> json) {
@@ -14,6 +18,8 @@ class SnmpScannerStatus {
isRunning: json['is_running'] as bool,
runningForSeconds: (json['running_for_seconds'] as num?)?.toDouble(),
nextRunInSeconds: (json['next_run_in_seconds'] as num?)?.toDouble(),
lastScanDevicesSeen: (json['last_scan_devices_seen'] as num?)?.toInt(),
lastScanSecondsAgo: (json['last_scan_seconds_ago'] as num?)?.toDouble(),
);
}
}
+13 -7
View File
@@ -24,14 +24,20 @@ class ArpScannerCard extends StatelessWidget {
ArpScannerStatus status,
double elapsed,
) {
final successColor =
Theme.of(context).extension<AppColorExtension>()!.success;
final successColor = Theme.of(
context,
).extension<AppColorExtension>()!.success;
final neutralColor = Theme.of(context).colorScheme.outline;
final lastScan = status.lastScanDevicesSeen != null
? '${status.lastScanDevicesSeen} devices on last scan'
: null;
if (status.isRunning) {
final sub = status.runningForSeconds != null
? ['Running for ${formatSeconds(status.runningForSeconds! + elapsed)}']
: <String>[];
return (color: successColor, label: 'Running', sublabels: sub);
final sublabels = <String>[
if (status.runningForSeconds != null)
'Running for ${formatSeconds(status.runningForSeconds! + elapsed)}',
?lastScan,
];
return (color: successColor, label: 'Running', sublabels: sublabels);
}
if (status.nextRunInSeconds != null) {
final remaining = (status.nextRunInSeconds! - elapsed).clamp(
@@ -41,7 +47,7 @@ class ArpScannerCard extends StatelessWidget {
return (
color: neutralColor,
label: 'Waiting for next run',
sublabels: ['Next run in ${formatSeconds(remaining)}'],
sublabels: ['Next run in ${formatSeconds(remaining)}', ?lastScan],
);
}
return (color: neutralColor, label: 'Not started', sublabels: []);
+10 -5
View File
@@ -28,11 +28,16 @@ class SnmpScannerCard extends StatelessWidget {
context,
).extension<AppColorExtension>()!.success;
final neutralColor = Theme.of(context).colorScheme.outline;
final lastScan = status.lastScanDevicesSeen != null
? '${status.lastScanDevicesSeen} devices on last scan'
: null;
if (status.isRunning) {
final sub = status.runningForSeconds != null
? ['Running for ${formatSeconds(status.runningForSeconds! + elapsed)}']
: <String>[];
return (color: successColor, label: 'Running', sublabels: sub);
final sublabels = <String>[
if (status.runningForSeconds != null)
'Running for ${formatSeconds(status.runningForSeconds! + elapsed)}',
?lastScan,
];
return (color: successColor, label: 'Running', sublabels: sublabels);
}
if (status.nextRunInSeconds != null) {
final remaining = (status.nextRunInSeconds! - elapsed).clamp(
@@ -42,7 +47,7 @@ class SnmpScannerCard extends StatelessWidget {
return (
color: neutralColor,
label: 'Waiting for next run',
sublabels: ['Next run in ${formatSeconds(remaining)}'],
sublabels: ['Next run in ${formatSeconds(remaining)}', ?lastScan],
);
}
return (color: neutralColor, label: 'Not started', sublabels: []);