Let the Reserve button pick another address of the lease's subnet

The Reserve dialog fills in the lease's address but lets it be changed;
POST /leases/{ip}/reserve takes an optional ip, steward lease reserve
takes --ip. The address must be in the lease's subnet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-25 00:29:07 +01:00
1 parent c7a64773a7
commit 1bc9f9f63a
8 files changed
+49 -12

No files matched your search

+21 -3
View File
@@ -114,7 +114,8 @@ func (s *Server) deleteLease(w http.ResponseWriter, r *http.Request, p *auth.Pri
return nil
}
// reserveLease makes a lease's address the client's reservation.
// reserveLease makes a lease's address (or another in its subnet) the
// client's reservation.
func (s *Server) reserveLease(w http.ResponseWriter, r *http.Request, p *auth.Principal) error {
ip, err := leaseIP(r)
if err != nil {
@@ -126,6 +127,8 @@ func (s *Server) reserveLease(w http.ResponseWriter, r *http.Request, p *auth.Pr
// By is how the reservation knows the client: mac, client_id or
// both (the default when the client sent a client ID).
By string `json:"by,omitempty"`
// IP reserves another address of the lease's subnet instead.
IP string `json:"ip,omitempty"`
}
if err := decodeOptional(r, &in); err != nil {
return err
@@ -161,10 +164,25 @@ func (s *Server) reserveLease(w http.ResponseWriter, r *http.Request, p *auth.Pr
} else if !validate.ValidHostname(host) {
host = ""
}
want := ip
if in.IP != "" {
if want, err = netip.ParseAddr(strings.TrimSpace(in.IP)); err != nil || !want.Is4() {
return badRequest("ip %q: not an IPv4 address", in.IP)
}
}
var id int64
return s.change(w, r, p, "reserve "+ip.String()+" for "+who, func(st *model.State, newID func() int64) error {
return s.change(w, r, p, "reserve "+want.String()+" for "+who, func(st *model.State, newID func() int64) error {
if want != ip {
if sub := st.SubnetFor(ip); sub == nil || !sub.Prefix.Contains(want) {
where := "the lease's subnet"
if sub != nil {
where = sub.Prefix.String()
}
return badRequest("%s isn't in %s", want, where)
}
}
id = newID()
st.Reservations = append(st.Reservations, &model.Reservation{ID: id, MAC: mac, ClientID: cid, IP: ip, Hostname: host, Options: []model.OptionValue{}, Comment: in.Comment})
st.Reservations = append(st.Reservations, &model.Reservation{ID: id, MAC: mac, ClientID: cid, IP: want, Hostname: host, Options: []model.OptionValue{}, Comment: in.Comment})
return nil
}, func(st *model.State) any {
if res := st.ReservationByID(id); res != nil {