Files
steve b6f8de1dcc lint: drive baseline to zero, drop only-new-issues gate
Cleanup pass over the repo so CI can enforce lint going forward
without the only-new-issues escape hatch:

* gofumpt -w across the tree (31 hits, all formatting)
* misspell --fix (25 hits, US-locale spelling) — but reverted on
  api.JobCancelled = "cancelled" since that literal is the wire +
  DB CHECK constraint value, plus matched the case in store/fleet.go
  back to "cancelled" and added //nolint:misspell on both for the
  next time someone reaches for the auto-fix
* Wrap every `defer rows.Close()` / `defer stmt.Close()` /
  `defer res.Body.Close()` in `defer func() { _ = .Close() }()`
  to satisfy errcheck without losing the close itself
* websocket.Dial callers (1 prod, 4 tests) now capture + close the
  upgrade response Body — coder/websocket can return res with a nil
  Body on success, so the test deferred-closes guard against that
* Annotate the two genuine-by-design nilerr cases with //nolint
  comments explaining why nil-on-error is the contract (cookie
  missing = no session; ctx cancelled mid-backoff = clean shutdown)
* Add brief godoc on the 10 exported const groups + types that
  revive flagged (api.HostOS/HostArch/JobKind/JobStatus/LogStream/
  ErrorCode, restic.EventKind, store.Role, web.FS)
* Drop the unused (*Server).userByID method
* Inline the unparam baseView(active) — every UI page is under
  the dashboard primary nav today

Result: `golangci-lint run ./...` reports 0 issues. CI lint job
no longer needs only-new-issues: true; X-06 follow-up entry in
tasks.md removed.
2026-05-03 16:15:17 +01:00

79 lines
2.3 KiB
Go

package store
import (
"context"
"fmt"
"time"
)
// FleetSummary is the aggregated view that powers the dashboard's
// summary tiles. All numbers are point-in-time snapshots; the
// caller polls.
type FleetSummary struct {
TotalHosts int
HostsOnline int
HostsDegraded int
HostsOffline int
RepoBytesTotal int64
SnapshotsTotal int
OpenAlerts int
// Last-24h job rollup. JobsTotal includes every status; the
// breakdown sums to it (modulo any in-flight queued/running).
JobsLast24h int
JobsLast24hSucceeded int
JobsLast24hFailed int
JobsLast24hCancelled int
}
// FleetSummary aggregates host + job stats in two queries. Cheap on
// SQLite at Phase 1 scale (12 hosts, a few hundred jobs/day) — no
// caching layer; the tile is regenerated on every dashboard render.
func (s *Store) FleetSummary(ctx context.Context) (FleetSummary, error) {
var fs FleetSummary
row := s.db.QueryRowContext(ctx, `
SELECT
COUNT(*),
COALESCE(SUM(CASE WHEN status = 'online' THEN 1 ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN status = 'degraded' THEN 1 ELSE 0 END), 0),
COALESCE(SUM(CASE WHEN status = 'offline' THEN 1 ELSE 0 END), 0),
COALESCE(SUM(repo_size_bytes), 0),
COALESCE(SUM(snapshot_count), 0),
COALESCE(SUM(open_alert_count), 0)
FROM hosts`)
if err := row.Scan(
&fs.TotalHosts, &fs.HostsOnline, &fs.HostsDegraded, &fs.HostsOffline,
&fs.RepoBytesTotal, &fs.SnapshotsTotal, &fs.OpenAlerts,
); err != nil {
return FleetSummary{}, fmt.Errorf("store: fleet summary hosts: %w", err)
}
cutoff := time.Now().Add(-24 * time.Hour).UTC().Format(time.RFC3339Nano)
rows, err := s.db.QueryContext(ctx,
`SELECT status, COUNT(*) FROM jobs WHERE created_at > ? GROUP BY status`,
cutoff)
if err != nil {
return FleetSummary{}, fmt.Errorf("store: fleet summary jobs: %w", err)
}
defer func() { _ = rows.Close() }()
for rows.Next() {
var status string
var n int
if err := rows.Scan(&status, &n); err != nil {
return FleetSummary{}, fmt.Errorf("store: fleet summary scan: %w", err)
}
fs.JobsLast24h += n
switch status {
case "succeeded":
fs.JobsLast24hSucceeded = n
case "failed":
fs.JobsLast24hFailed = n
case "cancelled": //nolint:misspell // matches the DB CHECK constraint and api.JobCancelled wire value
fs.JobsLast24hCancelled = n
}
}
return fs, rows.Err()
}