Hidden interfaces, last-seen pings, activity by subnet

- interfaces.exclude in steward.yaml (names or shell patterns) leaves
  interfaces out of GET /interfaces, the web UI's picker and
  'steward interface list'. Subnets on them are still served.
- With the ping check on, active leases are pinged every minute; a
  client that answers gets a last-seen time (leases.last_seen,
  migration 4). Not answering never takes a client offline.
- The activity log filters by subnet (--subnet, the Activity page,
  GET /events?subnet=).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-18 17:50:21 +01:00
1 parent 6ad1bb9a0d
commit 5e6c61cc8e
31 files changed
+358 -39

No files matched your search

+7 -6
View File
@@ -10,7 +10,6 @@ import (
"net"
"net/netip"
"sort"
"strings"
"sync"
"sync/atomic"
"time"
@@ -24,6 +23,7 @@ type LeaseStore interface {
PutLease(ctx context.Context, l *model.Lease) error
DeleteLease(ctx context.Context, ip netip.Addr) error
PruneLeases(ctx context.Context, before time.Time) (int64, error)
SetLastSeen(ctx context.Context, seen map[netip.Addr]time.Time) error
}
// Interfaces reports a network interface's IPv4 addresses with their
@@ -32,7 +32,8 @@ type Interfaces interface {
Addrs(name string) ([]netip.Prefix, error)
}
// Pinger checks whether an address answers (conflict detection).
// Pinger checks whether an address answers (conflict detection, and
// clients' last-seen times).
type Pinger interface {
Ping(ctx context.Context, a netip.Addr, timeout time.Duration) bool
}
@@ -342,15 +343,15 @@ func (e *events) add(ev Event) {
e.counts[ev.Type]++
}
// Events returns the latest events, newest first, optionally only those of
// one MAC address or interface.
func (s *Server) Events(limit int, mac, iface string) []Event {
// Events returns the latest events, newest first, optionally only those
// keep accepts.
func (s *Server) Events(limit int, keep func(Event) bool) []Event {
s.events.mu.Lock()
defer s.events.mu.Unlock()
out := []Event{}
for i := len(s.events.list) - 1; i >= 0 && (limit <= 0 || len(out) < limit); i-- {
ev := s.events.list[i]
if mac != "" && !strings.EqualFold(ev.MAC, mac) || iface != "" && ev.Interface != iface {
if keep != nil && !keep(ev) {
continue
}
out = append(out, ev)