mirror of
https://github.com/rzuasti/oott.git
synced 2026-07-08 19:21:54 +02:00
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:
co-authored by
Claude Opus 4.8
parent
38158a4ffc
commit
d15411a889
@@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
- [x] Add configuration options to enable/disable each scanner
|
- [x] Add configuration options to enable/disable each scanner
|
||||||
- [ ] Implement the pushover API call directly to support HTML content and review notification text to use it
|
- [ ] Implement the pushover API call directly to support HTML content and review notification text to use it
|
||||||
- [ ] Add "devices seen on last scan" to the ARP and SNMP scanners status
|
- [x] Add "devices seen on last scan" to the ARP and SNMP scanners status
|
||||||
|
- [ ] Modify the passive scanners status to count devices seen in the last hour
|
||||||
|
- [ ] Modify the event management to ignore duplicate events if they happen within a time threshold
|
||||||
- [x] Review the recommended timings for the ARP scanner (change defaults) — SNMP defaults set to 10m/5s
|
- [x] Review the recommended timings for the ARP scanner (change defaults) — SNMP defaults set to 10m/5s
|
||||||
|
|
||||||
## Frontend
|
## Frontend
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ pub async fn scan() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
info!("Done with ARP probes");
|
info!("Done with ARP probes");
|
||||||
info!("Found {} online devices", devices.len());
|
info!("Found {} online devices", devices.len());
|
||||||
|
status::record_scan(devices.len() as u64);
|
||||||
|
|
||||||
// Process found devices
|
// Process found devices
|
||||||
for device in devices.iter() {
|
for device in devices.iter() {
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ pub struct ArpScannerStatus {
|
|||||||
pub is_running: bool,
|
pub is_running: bool,
|
||||||
pub scan_started_at: Option<DateTime<Utc>>,
|
pub scan_started_at: Option<DateTime<Utc>>,
|
||||||
pub next_scan_at: Option<DateTime<Utc>>,
|
pub next_scan_at: Option<DateTime<Utc>>,
|
||||||
|
pub last_scan_devices_seen: Option<u64>,
|
||||||
|
pub last_scan_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -13,6 +15,8 @@ pub struct ArpScannerStatusSnapshot {
|
|||||||
pub is_running: bool,
|
pub is_running: bool,
|
||||||
pub scan_started_at: Option<DateTime<Utc>>,
|
pub scan_started_at: Option<DateTime<Utc>>,
|
||||||
pub next_scan_at: Option<DateTime<Utc>>,
|
pub next_scan_at: Option<DateTime<Utc>>,
|
||||||
|
pub last_scan_devices_seen: Option<u64>,
|
||||||
|
pub last_scan_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static STATUS: OnceCell<Mutex<ArpScannerStatus>> = OnceCell::new();
|
static STATUS: OnceCell<Mutex<ArpScannerStatus>> = OnceCell::new();
|
||||||
@@ -23,6 +27,8 @@ pub fn init() {
|
|||||||
is_running: false,
|
is_running: false,
|
||||||
scan_started_at: None,
|
scan_started_at: None,
|
||||||
next_scan_at: None,
|
next_scan_at: None,
|
||||||
|
last_scan_devices_seen: None,
|
||||||
|
last_scan_at: None,
|
||||||
}))
|
}))
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -45,6 +51,14 @@ pub fn set_waiting(next_scan_at: DateTime<Utc>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record_scan(devices_seen: u64) {
|
||||||
|
if let Some(m) = STATUS.get() {
|
||||||
|
let mut s = m.lock().unwrap();
|
||||||
|
s.last_scan_devices_seen = Some(devices_seen);
|
||||||
|
s.last_scan_at = Some(Utc::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get() -> Option<ArpScannerStatusSnapshot> {
|
pub fn get() -> Option<ArpScannerStatusSnapshot> {
|
||||||
STATUS.get().map(|m| {
|
STATUS.get().map(|m| {
|
||||||
let s = m.lock().unwrap();
|
let s = m.lock().unwrap();
|
||||||
@@ -52,6 +66,8 @@ pub fn get() -> Option<ArpScannerStatusSnapshot> {
|
|||||||
is_running: s.is_running,
|
is_running: s.is_running,
|
||||||
scan_started_at: s.scan_started_at,
|
scan_started_at: s.scan_started_at,
|
||||||
next_scan_at: s.next_scan_at,
|
next_scan_at: s.next_scan_at,
|
||||||
|
last_scan_devices_seen: s.last_scan_devices_seen,
|
||||||
|
last_scan_at: s.last_scan_at,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -66,6 +82,8 @@ mod tests {
|
|||||||
s.is_running = false;
|
s.is_running = false;
|
||||||
s.scan_started_at = None;
|
s.scan_started_at = None;
|
||||||
s.next_scan_at = None;
|
s.next_scan_at = None;
|
||||||
|
s.last_scan_devices_seen = None;
|
||||||
|
s.last_scan_at = None;
|
||||||
} else {
|
} else {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@@ -92,6 +110,15 @@ mod tests {
|
|||||||
assert_eq!(snapshot.next_scan_at.unwrap(), next);
|
assert_eq!(snapshot.next_scan_at.unwrap(), next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_record_scan() {
|
||||||
|
reset_for_test();
|
||||||
|
record_scan(7);
|
||||||
|
let snapshot = get().unwrap();
|
||||||
|
assert_eq!(snapshot.last_scan_devices_seen, Some(7));
|
||||||
|
assert!(snapshot.last_scan_at.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_initial_state() {
|
fn test_initial_state() {
|
||||||
reset_for_test();
|
reset_for_test();
|
||||||
@@ -99,5 +126,7 @@ mod tests {
|
|||||||
assert!(!snapshot.is_running);
|
assert!(!snapshot.is_running);
|
||||||
assert!(snapshot.scan_started_at.is_none());
|
assert!(snapshot.scan_started_at.is_none());
|
||||||
assert!(snapshot.next_scan_at.is_none());
|
assert!(snapshot.next_scan_at.is_none());
|
||||||
|
assert!(snapshot.last_scan_devices_seen.is_none());
|
||||||
|
assert!(snapshot.last_scan_at.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ pub async fn scan() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
match finder::find(config).await {
|
match finder::find(config).await {
|
||||||
Ok(devices) => {
|
Ok(devices) => {
|
||||||
info!("SNMP poll found {} devices in the ARP cache", devices.len());
|
info!("SNMP poll found {} devices in the ARP cache", devices.len());
|
||||||
|
status::record_scan(devices.len() as u64);
|
||||||
for device in devices.iter() {
|
for device in devices.iter() {
|
||||||
process_device(device);
|
process_device(device);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ pub struct SnmpScannerStatus {
|
|||||||
pub is_running: bool,
|
pub is_running: bool,
|
||||||
pub scan_started_at: Option<DateTime<Utc>>,
|
pub scan_started_at: Option<DateTime<Utc>>,
|
||||||
pub next_scan_at: Option<DateTime<Utc>>,
|
pub next_scan_at: Option<DateTime<Utc>>,
|
||||||
|
pub last_scan_devices_seen: Option<u64>,
|
||||||
|
pub last_scan_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
@@ -13,6 +15,8 @@ pub struct SnmpScannerStatusSnapshot {
|
|||||||
pub is_running: bool,
|
pub is_running: bool,
|
||||||
pub scan_started_at: Option<DateTime<Utc>>,
|
pub scan_started_at: Option<DateTime<Utc>>,
|
||||||
pub next_scan_at: Option<DateTime<Utc>>,
|
pub next_scan_at: Option<DateTime<Utc>>,
|
||||||
|
pub last_scan_devices_seen: Option<u64>,
|
||||||
|
pub last_scan_at: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
static STATUS: OnceCell<Mutex<SnmpScannerStatus>> = OnceCell::new();
|
static STATUS: OnceCell<Mutex<SnmpScannerStatus>> = OnceCell::new();
|
||||||
@@ -23,6 +27,8 @@ pub fn init() {
|
|||||||
is_running: false,
|
is_running: false,
|
||||||
scan_started_at: None,
|
scan_started_at: None,
|
||||||
next_scan_at: None,
|
next_scan_at: None,
|
||||||
|
last_scan_devices_seen: None,
|
||||||
|
last_scan_at: None,
|
||||||
}))
|
}))
|
||||||
.ok();
|
.ok();
|
||||||
}
|
}
|
||||||
@@ -45,6 +51,14 @@ pub fn set_waiting(next_scan_at: DateTime<Utc>) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn record_scan(devices_seen: u64) {
|
||||||
|
if let Some(m) = STATUS.get() {
|
||||||
|
let mut s = m.lock().unwrap();
|
||||||
|
s.last_scan_devices_seen = Some(devices_seen);
|
||||||
|
s.last_scan_at = Some(Utc::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get() -> Option<SnmpScannerStatusSnapshot> {
|
pub fn get() -> Option<SnmpScannerStatusSnapshot> {
|
||||||
STATUS.get().map(|m| {
|
STATUS.get().map(|m| {
|
||||||
let s = m.lock().unwrap();
|
let s = m.lock().unwrap();
|
||||||
@@ -52,6 +66,8 @@ pub fn get() -> Option<SnmpScannerStatusSnapshot> {
|
|||||||
is_running: s.is_running,
|
is_running: s.is_running,
|
||||||
scan_started_at: s.scan_started_at,
|
scan_started_at: s.scan_started_at,
|
||||||
next_scan_at: s.next_scan_at,
|
next_scan_at: s.next_scan_at,
|
||||||
|
last_scan_devices_seen: s.last_scan_devices_seen,
|
||||||
|
last_scan_at: s.last_scan_at,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -66,6 +82,8 @@ mod tests {
|
|||||||
s.is_running = false;
|
s.is_running = false;
|
||||||
s.scan_started_at = None;
|
s.scan_started_at = None;
|
||||||
s.next_scan_at = None;
|
s.next_scan_at = None;
|
||||||
|
s.last_scan_devices_seen = None;
|
||||||
|
s.last_scan_at = None;
|
||||||
} else {
|
} else {
|
||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
@@ -92,6 +110,15 @@ mod tests {
|
|||||||
assert_eq!(snapshot.next_scan_at.unwrap(), next);
|
assert_eq!(snapshot.next_scan_at.unwrap(), next);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_record_scan() {
|
||||||
|
reset_for_test();
|
||||||
|
record_scan(7);
|
||||||
|
let snapshot = get().unwrap();
|
||||||
|
assert_eq!(snapshot.last_scan_devices_seen, Some(7));
|
||||||
|
assert!(snapshot.last_scan_at.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_initial_state() {
|
fn test_initial_state() {
|
||||||
reset_for_test();
|
reset_for_test();
|
||||||
@@ -99,5 +126,7 @@ mod tests {
|
|||||||
assert!(!snapshot.is_running);
|
assert!(!snapshot.is_running);
|
||||||
assert!(snapshot.scan_started_at.is_none());
|
assert!(snapshot.scan_started_at.is_none());
|
||||||
assert!(snapshot.next_scan_at.is_none());
|
assert!(snapshot.next_scan_at.is_none());
|
||||||
|
assert!(snapshot.last_scan_devices_seen.is_none());
|
||||||
|
assert!(snapshot.last_scan_at.is_none());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ pub struct ArpScannerStatusResponse {
|
|||||||
pub running_for_seconds: Option<f64>,
|
pub running_for_seconds: Option<f64>,
|
||||||
/// Seconds until the next scan starts (only set when is_running is false; clamped to 0)
|
/// Seconds until the next scan starts (only set when is_running is false; clamped to 0)
|
||||||
pub next_run_in_seconds: Option<f64>,
|
pub next_run_in_seconds: Option<f64>,
|
||||||
|
/// Number of devices found by the last successful scan (None if none completed yet)
|
||||||
|
pub last_scan_devices_seen: Option<u64>,
|
||||||
|
/// Seconds since the last successful scan completed (None if none completed yet)
|
||||||
|
pub last_scan_seconds_ago: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
@@ -47,9 +51,15 @@ pub async fn status() -> Result<Json<ArpScannerStatusResponse>, StatusCode> {
|
|||||||
(None, next_run_in)
|
(None, next_run_in)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let last_scan_seconds_ago = snapshot
|
||||||
|
.last_scan_at
|
||||||
|
.map(|t| ((now - t).num_milliseconds() as f64 / 1000.0).max(0.0));
|
||||||
|
|
||||||
Ok(Json(ArpScannerStatusResponse {
|
Ok(Json(ArpScannerStatusResponse {
|
||||||
is_running: snapshot.is_running,
|
is_running: snapshot.is_running,
|
||||||
running_for_seconds,
|
running_for_seconds,
|
||||||
next_run_in_seconds,
|
next_run_in_seconds,
|
||||||
|
last_scan_devices_seen: snapshot.last_scan_devices_seen,
|
||||||
|
last_scan_seconds_ago,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ pub struct SnmpScannerStatusResponse {
|
|||||||
pub running_for_seconds: Option<f64>,
|
pub running_for_seconds: Option<f64>,
|
||||||
/// Seconds until the next poll starts (only set when is_running is false; clamped to 0)
|
/// Seconds until the next poll starts (only set when is_running is false; clamped to 0)
|
||||||
pub next_run_in_seconds: Option<f64>,
|
pub next_run_in_seconds: Option<f64>,
|
||||||
|
/// Number of devices found by the last successful scan (None if none completed yet)
|
||||||
|
pub last_scan_devices_seen: Option<u64>,
|
||||||
|
/// Seconds since the last successful scan completed (None if none completed yet)
|
||||||
|
pub last_scan_seconds_ago: Option<f64>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
@@ -47,9 +51,15 @@ pub async fn status() -> Result<Json<SnmpScannerStatusResponse>, StatusCode> {
|
|||||||
(None, next_run_in)
|
(None, next_run_in)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let last_scan_seconds_ago = snapshot
|
||||||
|
.last_scan_at
|
||||||
|
.map(|t| ((now - t).num_milliseconds() as f64 / 1000.0).max(0.0));
|
||||||
|
|
||||||
Ok(Json(SnmpScannerStatusResponse {
|
Ok(Json(SnmpScannerStatusResponse {
|
||||||
is_running: snapshot.is_running,
|
is_running: snapshot.is_running,
|
||||||
running_for_seconds,
|
running_for_seconds,
|
||||||
next_run_in_seconds,
|
next_run_in_seconds,
|
||||||
|
last_scan_devices_seen: snapshot.last_scan_devices_seen,
|
||||||
|
last_scan_seconds_ago,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,11 +2,15 @@ class ArpScannerStatus {
|
|||||||
final bool isRunning;
|
final bool isRunning;
|
||||||
final double? runningForSeconds;
|
final double? runningForSeconds;
|
||||||
final double? nextRunInSeconds;
|
final double? nextRunInSeconds;
|
||||||
|
final int? lastScanDevicesSeen;
|
||||||
|
final double? lastScanSecondsAgo;
|
||||||
|
|
||||||
const ArpScannerStatus({
|
const ArpScannerStatus({
|
||||||
required this.isRunning,
|
required this.isRunning,
|
||||||
this.runningForSeconds,
|
this.runningForSeconds,
|
||||||
this.nextRunInSeconds,
|
this.nextRunInSeconds,
|
||||||
|
this.lastScanDevicesSeen,
|
||||||
|
this.lastScanSecondsAgo,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory ArpScannerStatus.fromJson(Map<String, dynamic> json) {
|
factory ArpScannerStatus.fromJson(Map<String, dynamic> json) {
|
||||||
@@ -14,6 +18,8 @@ class ArpScannerStatus {
|
|||||||
isRunning: json['is_running'] as bool,
|
isRunning: json['is_running'] as bool,
|
||||||
runningForSeconds: (json['running_for_seconds'] as num?)?.toDouble(),
|
runningForSeconds: (json['running_for_seconds'] as num?)?.toDouble(),
|
||||||
nextRunInSeconds: (json['next_run_in_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 bool isRunning;
|
||||||
final double? runningForSeconds;
|
final double? runningForSeconds;
|
||||||
final double? nextRunInSeconds;
|
final double? nextRunInSeconds;
|
||||||
|
final int? lastScanDevicesSeen;
|
||||||
|
final double? lastScanSecondsAgo;
|
||||||
|
|
||||||
const SnmpScannerStatus({
|
const SnmpScannerStatus({
|
||||||
required this.isRunning,
|
required this.isRunning,
|
||||||
this.runningForSeconds,
|
this.runningForSeconds,
|
||||||
this.nextRunInSeconds,
|
this.nextRunInSeconds,
|
||||||
|
this.lastScanDevicesSeen,
|
||||||
|
this.lastScanSecondsAgo,
|
||||||
});
|
});
|
||||||
|
|
||||||
factory SnmpScannerStatus.fromJson(Map<String, dynamic> json) {
|
factory SnmpScannerStatus.fromJson(Map<String, dynamic> json) {
|
||||||
@@ -14,6 +18,8 @@ class SnmpScannerStatus {
|
|||||||
isRunning: json['is_running'] as bool,
|
isRunning: json['is_running'] as bool,
|
||||||
runningForSeconds: (json['running_for_seconds'] as num?)?.toDouble(),
|
runningForSeconds: (json['running_for_seconds'] as num?)?.toDouble(),
|
||||||
nextRunInSeconds: (json['next_run_in_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(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,14 +24,20 @@ class ArpScannerCard extends StatelessWidget {
|
|||||||
ArpScannerStatus status,
|
ArpScannerStatus status,
|
||||||
double elapsed,
|
double elapsed,
|
||||||
) {
|
) {
|
||||||
final successColor =
|
final successColor = Theme.of(
|
||||||
Theme.of(context).extension<AppColorExtension>()!.success;
|
context,
|
||||||
|
).extension<AppColorExtension>()!.success;
|
||||||
final neutralColor = Theme.of(context).colorScheme.outline;
|
final neutralColor = Theme.of(context).colorScheme.outline;
|
||||||
|
final lastScan = status.lastScanDevicesSeen != null
|
||||||
|
? '${status.lastScanDevicesSeen} devices on last scan'
|
||||||
|
: null;
|
||||||
if (status.isRunning) {
|
if (status.isRunning) {
|
||||||
final sub = status.runningForSeconds != null
|
final sublabels = <String>[
|
||||||
? ['Running for ${formatSeconds(status.runningForSeconds! + elapsed)}']
|
if (status.runningForSeconds != null)
|
||||||
: <String>[];
|
'Running for ${formatSeconds(status.runningForSeconds! + elapsed)}',
|
||||||
return (color: successColor, label: 'Running', sublabels: sub);
|
?lastScan,
|
||||||
|
];
|
||||||
|
return (color: successColor, label: 'Running', sublabels: sublabels);
|
||||||
}
|
}
|
||||||
if (status.nextRunInSeconds != null) {
|
if (status.nextRunInSeconds != null) {
|
||||||
final remaining = (status.nextRunInSeconds! - elapsed).clamp(
|
final remaining = (status.nextRunInSeconds! - elapsed).clamp(
|
||||||
@@ -41,7 +47,7 @@ class ArpScannerCard extends StatelessWidget {
|
|||||||
return (
|
return (
|
||||||
color: neutralColor,
|
color: neutralColor,
|
||||||
label: 'Waiting for next run',
|
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: []);
|
return (color: neutralColor, label: 'Not started', sublabels: []);
|
||||||
|
|||||||
@@ -28,11 +28,16 @@ class SnmpScannerCard extends StatelessWidget {
|
|||||||
context,
|
context,
|
||||||
).extension<AppColorExtension>()!.success;
|
).extension<AppColorExtension>()!.success;
|
||||||
final neutralColor = Theme.of(context).colorScheme.outline;
|
final neutralColor = Theme.of(context).colorScheme.outline;
|
||||||
|
final lastScan = status.lastScanDevicesSeen != null
|
||||||
|
? '${status.lastScanDevicesSeen} devices on last scan'
|
||||||
|
: null;
|
||||||
if (status.isRunning) {
|
if (status.isRunning) {
|
||||||
final sub = status.runningForSeconds != null
|
final sublabels = <String>[
|
||||||
? ['Running for ${formatSeconds(status.runningForSeconds! + elapsed)}']
|
if (status.runningForSeconds != null)
|
||||||
: <String>[];
|
'Running for ${formatSeconds(status.runningForSeconds! + elapsed)}',
|
||||||
return (color: successColor, label: 'Running', sublabels: sub);
|
?lastScan,
|
||||||
|
];
|
||||||
|
return (color: successColor, label: 'Running', sublabels: sublabels);
|
||||||
}
|
}
|
||||||
if (status.nextRunInSeconds != null) {
|
if (status.nextRunInSeconds != null) {
|
||||||
final remaining = (status.nextRunInSeconds! - elapsed).clamp(
|
final remaining = (status.nextRunInSeconds! - elapsed).clamp(
|
||||||
@@ -42,7 +47,7 @@ class SnmpScannerCard extends StatelessWidget {
|
|||||||
return (
|
return (
|
||||||
color: neutralColor,
|
color: neutralColor,
|
||||||
label: 'Waiting for next run',
|
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: []);
|
return (color: neutralColor, label: 'Not started', sublabels: []);
|
||||||
|
|||||||
Reference in New Issue
Block a user