Files
nmon/internal/store/setup_state.go
Vibe Coder f31fb3712a
Some checks failed
ci / lint (push) Successful in 4m19s
ci / openapi (push) Successful in 29s
ci / postgres (push) Successful in 4m55s
ci / test (push) Failing after 19m48s
Add settings forms, first-install wizard, webhook presets, help and poll tools
Make nmon easier to install and run without editing YAML by hand:

- Settings: point-and-click forms over config.yaml (GET/PUT
  /api/v1/settings/form); the YAML editor moves to /settings/yaml.
- First install: with no sign-in configured nmon starts in install
  mode, serves only the setup wizard (sign-in, basics, devices,
  alerting) and closes it for good once sign-in is saved. A
  self-signed certificate is generated when no TLS source is given.
  poller.interval becomes a config setting.
- Reverse proxies: opt-in --http-listen plain-HTTP listener beside
  HTTPS; sign-in stays required.
- Webhooks: Generic, Teams Adaptive Card, Slack and Discord presets,
  template preview and test send.
- Help: the operations, coverage and profile docs served at /help.
- Polling: per-device "Poll now", a dry-run test poll returning an
  SNMP trace, and time-limited in-memory SNMP debug recording, in the
  UI and as `nmon device poll`.
- Ansible: deploys into install mode when no sign-in is set, and no
  longer overwrites the config.yaml the wizard wrote.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017TuKgyKve2XLFtrrXWMi2w
2026-10-25 06:31:12 +00:00

38 lines
1.3 KiB
Go

package store
import (
"context"
"errors"
"time"
)
// setupState is the live model of setup_state (migration 16).
type setupState = m0016SetupState
// SetupState records whether first-install setup has finished. The flag only ever goes from false to true: there
// is deliberately no way to clear it, so install mode (which runs without sign-in) cannot be reopened.
type SetupState interface {
// SetupCompleted reports whether setup was marked completed, and when.
SetupCompleted(ctx context.Context) (bool, time.Time, error)
// MarkSetupCompleted records completion by who. It is idempotent: the first completion time is kept.
MarkSetupCompleted(ctx context.Context, by string) error
}
func (s *bunStore) SetupCompleted(ctx context.Context) (bool, time.Time, error) {
var row setupState
err := s.db.NewSelect().Model(&row).Where("id = 1").Limit(1).Scan(ctx)
if err != nil {
if errors.Is(mapErr(err), ErrNotFound) {
return false, time.Time{}, nil
}
return false, time.Time{}, mapErr(err)
}
return true, row.CompletedAt, nil
}
func (s *bunStore) MarkSetupCompleted(ctx context.Context, by string) error {
row := &setupState{ID: 1, CompletedAt: s.now(), CompletedBy: by}
_, err := s.db.NewInsert().Model(row).On("CONFLICT (id) DO NOTHING").Exec(ctx)
return mapErr(err)
}