Disable reservations; subnet enable/disable buttons; page-head CSS fix
release / release (push) Successful in 5m53s

- A disabled reservation is kept but ignored: the client gets a pool
  address like any other (steward reservation disable|enable, buttons and
  a checkbox in the web UI). Schema migration 3.
- Subnets get Disable/Enable buttons on their page and in the list.
- A rule left without its selector in style.css broke the page heading's
  layout, so the New ... buttons sat cramped under the description.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
abrown38andClaude Opus 5.5 committed 2026-10-14 16:02:15 +01:00
1 parent 7b3f0f764c
commit 6ad1bb9a0d
21 files changed
+165 -37

No files matched your search

+7 -5
View File
@@ -50,7 +50,7 @@ func (s *Store) LoadState(ctx context.Context) (*model.State, error) {
if err := rows.Err(); err != nil {
return nil, err
}
rows, err = s.db.QueryContext(ctx, `SELECT id, mac, client_id, ip, hostname, options, comment FROM reservations ORDER BY id`)
rows, err = s.db.QueryContext(ctx, `SELECT id, mac, client_id, ip, hostname, options, disabled, comment FROM reservations ORDER BY id`)
if err != nil {
return nil, err
}
@@ -58,10 +58,12 @@ func (s *Store) LoadState(ctx context.Context) (*model.State, error) {
for rows.Next() {
r := &model.Reservation{}
var ip, opts string
if err := rows.Scan(&r.ID, &r.MAC, &r.ClientID, &ip, &r.Hostname, &opts, &r.Comment); err != nil {
var disabled int
if err := rows.Scan(&r.ID, &r.MAC, &r.ClientID, &ip, &r.Hostname, &opts, &disabled, &r.Comment); err != nil {
return nil, err
}
r.IP, _ = netip.ParseAddr(ip)
r.Disabled = disabled != 0
if err := json.Unmarshal([]byte(opts), &r.Options); err != nil {
return nil, fmt.Errorf("reservation %d options: %w", r.ID, err)
}
@@ -158,10 +160,10 @@ func applyChange(ctx context.Context, tx *sql.Tx, c model.Change) error {
if err := json.Unmarshal(c.After, &r); err != nil {
return err
}
_, err := tx.ExecContext(ctx, `INSERT INTO reservations (id, mac, client_id, ip, hostname, options, comment) VALUES (?,?,?,?,?,?,?)
_, err := tx.ExecContext(ctx, `INSERT INTO reservations (id, mac, client_id, ip, hostname, options, disabled, comment) VALUES (?,?,?,?,?,?,?,?)
ON CONFLICT(id) DO UPDATE SET mac = excluded.mac, client_id = excluded.client_id, ip = excluded.ip, hostname = excluded.hostname,
options = excluded.options, comment = excluded.comment`,
r.ID, r.MAC, r.ClientID, r.IP.String(), r.Hostname, jsonText(nonNilOpts(r.Options)), r.Comment)
options = excluded.options, disabled = excluded.disabled, comment = excluded.comment`,
r.ID, r.MAC, r.ClientID, r.IP.String(), r.Hostname, jsonText(nonNilOpts(r.Options)), boolInt(r.Disabled), r.Comment)
return err
}
return fmt.Errorf("unknown kind %q", c.Kind)
+4
View File
@@ -193,6 +193,10 @@ CREATE INDEX audit_time ON audit (time);
ALTER TABLE tokens ADD COLUMN rotated_at TEXT;
ALTER TABLE tokens ADD COLUMN prev_secret_hash TEXT NOT NULL DEFAULT '';
ALTER TABLE tokens ADD COLUMN prev_expires_at TEXT;
`,
// 3: disabled reservations.
`
ALTER TABLE reservations ADD COLUMN disabled INTEGER NOT NULL DEFAULT 0;
`,
}
+2 -2
View File
@@ -35,7 +35,7 @@ func TestStateHistoryAndLeases(t *testing.T) {
sn := &model.Subnet{ID: ids[0], Name: "office", Interface: "eth0", Prefix: netip.MustParsePrefix("10.0.10.0/24"),
Pools: []model.Pool{{Start: netip.MustParseAddr("10.0.10.100"), End: netip.MustParseAddr("10.0.10.199")}},
Options: []model.OptionValue{{Code: 3, Value: "10.0.10.1"}}}
r := &model.Reservation{ID: ids[1], MAC: "aa:bb:cc:dd:ee:01", IP: netip.MustParseAddr("10.0.10.20"), Hostname: "nas"}
r := &model.Reservation{ID: ids[1], MAC: "aa:bb:cc:dd:ee:01", IP: netip.MustParseAddr("10.0.10.20"), Hostname: "nas", Disabled: true}
set := st.Settings
set.LeaseTime = 3600
e := &model.HistoryEntry{Time: time.Now(), Username: "admin", Via: "cli", Description: "add office",
@@ -51,7 +51,7 @@ func TestStateHistoryAndLeases(t *testing.T) {
if len(st.Subnets) != 1 || st.Subnets[0].Pools[0].Size() != 100 || st.Subnets[0].Options[0].Value != "10.0.10.1" {
t.Errorf("subnets %+v", st.Subnets)
}
if len(st.Reservations) != 1 || st.Reservations[0].Hostname != "nas" || st.Settings.LeaseTime != 3600 {
if len(st.Reservations) != 1 || st.Reservations[0].Hostname != "nas" || !st.Reservations[0].Disabled || st.Settings.LeaseTime != 3600 {
t.Errorf("reservations %+v settings %+v", st.Reservations, st.Settings)
}
// Undo: the inverse changes, marked as a revert.