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:
1 parent
6ad1bb9a0d
commit
5e6c61cc8e
31 files changed
+358
-39
No files matched your search
@@ -51,13 +51,13 @@ func leaseCmd() *cobra.Command {
|
||||
ls = keep
|
||||
}
|
||||
return output(ls, func(w io.Writer) {
|
||||
fmt.Fprintln(w, "IP\tMAC\tHOSTNAME\tSTATE\tENDS\tINTERFACE\tSUBNET")
|
||||
fmt.Fprintln(w, "IP\tMAC\tHOSTNAME\tSTATE\tENDS\tLAST SEEN\tINTERFACE\tSUBNET")
|
||||
for _, l := range ls {
|
||||
st := l.State
|
||||
if l.Reservation != 0 {
|
||||
st += ", reserved"
|
||||
}
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", l.IP, orDash(l.MAC), leaseHost(l), st, fmtWhen(l.End), l.Interface, orDash(l.SubnetName))
|
||||
fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n", l.IP, orDash(l.MAC), leaseHost(l), st, fmtWhen(l.End), fmtSeen(l.LastSeen), l.Interface, orDash(l.SubnetName))
|
||||
}
|
||||
})
|
||||
},
|
||||
@@ -91,6 +91,9 @@ func leaseCmd() *cobra.Command {
|
||||
fmt.Fprintf(w, "Since:\t%s\n", l.Start.Local().Format("2006-01-02 15:04:05"))
|
||||
fmt.Fprintf(w, "Ends:\t%s (%s)\n", l.End.Local().Format("2006-01-02 15:04:05"), fmtWhen(l.End))
|
||||
fmt.Fprintf(w, "Interface:\t%s\n", l.Interface)
|
||||
if l.LastSeen != nil {
|
||||
fmt.Fprintf(w, "Last seen:\t%s (%s, answered a ping)\n", l.LastSeen.Local().Format("2006-01-02 15:04:05"), fmtWhen(*l.LastSeen))
|
||||
}
|
||||
if l.Reservation != 0 {
|
||||
fmt.Fprintf(w, "Reservation:\t%d\n", l.Reservation)
|
||||
}
|
||||
@@ -157,7 +160,7 @@ then on. The host name the client sent becomes the reservation's, unless
|
||||
}
|
||||
|
||||
func activityCmd() *cobra.Command {
|
||||
var mac, iface string
|
||||
var mac, subnet, iface string
|
||||
var limit int
|
||||
var follow bool
|
||||
cmd := &cobra.Command{
|
||||
@@ -193,7 +196,7 @@ func activityCmd() *cobra.Command {
|
||||
}
|
||||
for {
|
||||
var evs []api.Event
|
||||
if err := c.Get(ctx(), "/events", q("mac", mac, "interface", iface, "limit", fmt.Sprint(limit)), &evs); err != nil {
|
||||
if err := c.Get(ctx(), "/events", q("mac", mac, "subnet", subnet, "interface", iface, "limit", fmt.Sprint(limit)), &evs); err != nil {
|
||||
return err
|
||||
}
|
||||
if flagOutput == "json" || flagOutput == "yaml" {
|
||||
@@ -212,6 +215,7 @@ func activityCmd() *cobra.Command {
|
||||
}
|
||||
f := cmd.Flags()
|
||||
f.StringVar(&mac, "mac", "", "only this client's")
|
||||
f.StringVar(&subnet, "subnet", "", "only this subnet's (ID, name or prefix); messages without an address, such as DISCOVERs, show for every subnet on their interface")
|
||||
f.StringVar(&iface, "interface", "", "only this interface's")
|
||||
f.IntVar(&limit, "limit", 50, "at most this many")
|
||||
f.BoolVarP(&follow, "follow", "f", false, "keep watching")
|
||||
@@ -336,7 +340,7 @@ reservation's own option of the same name wins.
|
||||
}
|
||||
f := set.Flags()
|
||||
f.StringVar(&leaseTime, "lease-time", "", "the default lease time (12h, 1d)")
|
||||
f.StringVar(&ping, "ping-check", "", "ping addresses before offering them: on or off")
|
||||
f.StringVar(&ping, "ping-check", "", "ping addresses before offering them, and clients every minute for their last-seen time: on or off")
|
||||
f.StringVar(&pingTimeout, "ping-timeout", "", "how long to wait for a ping's answer (500ms)")
|
||||
f.StringVar(&hold, "decline-hold", "", "how long an address in use by someone else is held back (24h)")
|
||||
f.StringVar(&authoritative, "authoritative", "", "refuse requests for addresses that don't belong on the interface: on or off")
|
||||
|
||||
@@ -424,6 +424,14 @@ func fmtWhen(t time.Time) string {
|
||||
return "in " + s
|
||||
}
|
||||
|
||||
// fmtSeen is a last-seen time, or "-" for a client never seen.
|
||||
func fmtSeen(t *time.Time) string {
|
||||
if t == nil {
|
||||
return "-"
|
||||
}
|
||||
return fmtWhen(*t)
|
||||
}
|
||||
|
||||
func orDash(s string) string {
|
||||
if s == "" {
|
||||
return "-"
|
||||
|
||||
@@ -339,6 +339,9 @@ func reservationCmd() *cobra.Command {
|
||||
lease := "-"
|
||||
if r.Lease != nil {
|
||||
lease = "until " + r.Lease.End.Local().Format("Jan 2 15:04")
|
||||
if r.Lease.LastSeen != nil {
|
||||
lease += ", seen " + fmtWhen(*r.Lease.LastSeen)
|
||||
}
|
||||
}
|
||||
state := "on"
|
||||
if r.Disabled {
|
||||
@@ -374,7 +377,10 @@ func reservationCmd() *cobra.Command {
|
||||
fmt.Fprintf(w, "Host name:\t%s\n", orDash(r.Hostname))
|
||||
fmt.Fprintln(w, "Options:\t"+optionsText(r.Options))
|
||||
if r.Lease != nil {
|
||||
fmt.Fprintf(w, "Lease:\tuntil %s, last seen on %s\n", r.Lease.End.Local().Format("2006-01-02 15:04"), r.Lease.Interface)
|
||||
fmt.Fprintf(w, "Lease:\tuntil %s, on %s\n", r.Lease.End.Local().Format("2006-01-02 15:04"), r.Lease.Interface)
|
||||
if r.Lease.LastSeen != nil {
|
||||
fmt.Fprintf(w, "Last seen:\t%s (answered a ping)\n", fmtWhen(*r.Lease.LastSeen))
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintln(w, "Lease:\tnone now")
|
||||
}
|
||||
|
||||
@@ -172,6 +172,7 @@ func serveCmd() *cobra.Command {
|
||||
Listen: a.cfg.Listen, TLSCertFile: a.cfg.TLS.CertFile, TLSKeyFile: a.cfg.TLS.KeyFile, TrustedProxies: a.cfg.Proxies(),
|
||||
SessionLifetime: time.Duration(a.cfg.Session.Lifetime), SessionIdle: time.Duration(a.cfg.Session.IdleTimeout),
|
||||
CookieSecure: a.cfg.Session.CookieSecure, Socket: a.cfg.Socket, PingProblem: pinger.Problem, ACME: acme,
|
||||
ExcludeInterfaces: a.cfg.Interfaces.Exclude,
|
||||
}, eng, d, host, a.log, web.FS())
|
||||
go d.Run(ctx)
|
||||
hup := make(chan os.Signal, 1)
|
||||
|
||||
Reference in new issue
Block a user