Skip to content

feat(hosts): host card scan link + working Group/Filters + server-persisted view preference#611

Merged
remyluslosius merged 4 commits into
mainfrom
feat/hosts-page-fixes
Jun 20, 2026
Merged

feat(hosts): host card scan link + working Group/Filters + server-persisted view preference#611
remyluslosius merged 4 commits into
mainfrom
feat/hosts-page-fixes

Conversation

@remyluslosius

Copy link
Copy Markdown
Contributor

Addresses three asks on the Host Management page (/hosts), each
spec-driven (spec -> tests -> code) and committed as its own stacked commit.

A — Host card chart icon links to the latest scan report

The previously-inert BarChart3 icon on each host card + table row is now a
ViewReportButton linking to /scans/{latest_scan_id}. Backend adds a
nullable latest_scan_id to the /hosts list item (newest completed
scan_run per host, one DISTINCT ON query — no N+1). The icon is hidden
when the host has no completed scan or the viewer lacks scan:read (the
destination is scan:read-gated).
Specs: api-hosts v1.6.0 C-13/AC-24, frontend-hosts-list C-09/AC-22.

B — Group + Filters actually work

  • Group: dropped the inert Team option (no backing host field; you
    chose Drop). None/Status/OS now partition the list into labelled
    sections (groupHosts): Status worst-first, OS alphabetical (Unknown last).
  • Filters: the dead Filters button is now a real popover with multi-select
    Status / Compliance-tier / OS facets, persisted to the URL and applied
    client-side (applyHostFilters: AND across dimensions, OR within), with an
    active-count badge + Clear all. Logic is in pure, unit-tested
    host-filtering.ts.
    Specs: frontend-hosts-list C-10/C-11, AC-23/AC-24.

C — Server-side per-user view preference (follows you across devices)

New system-user-preferences spec + a general preferences service:
migration 0040 (users.preferences JSONB), internal/userpref (Get +
shallow JSONB-|| Merge), and self-scoped GET/PATCH /api/v1/users/me/preferences (user id from identity, 401 anon, enum-validated,
unknown keys dropped). The frontend usePreferencesStore now hydrates from +
writes through to the server (localStorage kept as cache), AppFrame
reconciles on mount, and the /hosts view toggle resolves ?view= first,
else the persisted hostsViewDefault — toggling persists the per-user default,
so a chosen view "becomes the default until changed".
Specs: system-user-preferences v1.0.0, frontend-settings C-06/AC-30,
frontend-hosts-list C-12/AC-25.

Validation

  • Backend: go build, go vet, full internal/server + internal/userpref
    integration suites green (DSN-gated, isolated pg on :5433).
  • Frontend: tsc --noEmit clean, full vitest suite green (315+).
  • Specs: specter check 111 specs pass; annotation hygiene 0 errors.
  • New backend ACs verified against the real test DB; new frontend ACs are
    source-inspection + behavioral unit tests.

Note: internal/server/openapi_embed.yaml is gitignored and regenerated from
api/openapi.yaml by make vet/make build (runs before go test in CI).

Backend (api-hosts v1.6.0 C-13/AC-24): GET /hosts items now carry a
nullable latest_scan_id = the newest COMPLETED scan_run id per host,
loaded with one DISTINCT ON query (no N+1). Queued/running-only and
never-scanned hosts resolve to null.

Frontend (frontend-hosts-list v1.7.0 C-09/AC-22): the previously-inert
chart icon on each host card + table row is now a ViewReportButton that
links to /scans/{latestScanId}. Hidden when the host has no completed
scan or the viewer lacks scan:read (the destination is scan:read-gated).
frontend-hosts-list v1.7.0 C-10/C-11, AC-23/AC-24.

Group: drop the inert 'Team' option (no backing host field) and actually
partition the list — groupHosts() sections by monitoring band (worst-first)
or OS (alphabetical, Unknown last) with labelled GroupHeaders; None stays
flat.

Filters: the dead Filters button becomes a real popover (FiltersControl)
with multi-select Status / Compliance-tier / OS facets, persisted to the
URL (status/os/tier params) and applied client-side via applyHostFilters
(AND across dimensions, OR within). Active-filter count on the button +
Clear all. Logic lives in pure, unit-tested host-filtering.ts.
…iew (Part C)

system-user-preferences v1.0.0 (new spec, 111 total).

Backend: migration 0040 adds users.preferences JSONB; internal/userpref
service (Get + shallow JSONB-|| Merge, scoped to one active user);
GET/PATCH /api/v1/users/me/preferences — self-scoped (user id from
identity, 401 for anonymous, no RBAC perm), unknown keys dropped via the
typed contract, invalid enums rejected 400. Wired pool-only in newHandlers
(never 503).

Frontend: usePreferencesStore now hydrates from + writes through to the
server (PATCH on each setter, hydrateFromServer on AppFrame mount),
keeping the ow-preferences localStorage cache for instant load + offline.
The /hosts view toggle resolves URL ?view= first, else the persisted
hostsViewDefault, and toggling persists the per-user default — so a chosen
view 'becomes the default until changed' and follows the user across
devices.

Specs: frontend-settings C-06/AC-30 (store now server-synced),
frontend-hosts-list C-12/AC-25 (view default). New error codes
validation.invalid_body / validation.invalid_value.
…-embed)

The embed copy is gitignored and was only refreshed by make build / make
vet's file prereq, so editing api/openapi.yaml + running 'make generate-api'
(the natural 'I changed the contract' command) left it stale — a bare
'go test ./internal/server/' then failed TestOpenAPIDocs_EmbeddedMatchesSource.

Close the gap at the regeneration point: generate-api now depends on the
embed target (re-copies when openapi.yaml is newer), and a //go:generate
directive lets 'go generate ./...' refresh it via standard tooling. The
drift test remains the backstop.
@remyluslosius remyluslosius merged commit f6f46cd into main Jun 20, 2026
13 checks passed
@remyluslosius remyluslosius deleted the feat/hosts-page-fixes branch June 20, 2026 13:40
remyluslosius added a commit that referenced this pull request Jun 20, 2026
- SESSION_LOG.md: 2026-06-20 entry for the /hosts page fixes (chart-icon
  scan link, working Group/Filters, server-side per-user view preference,
  embed-staleness build fix), with next-steps + gotchas.
- BACKLOG.md: note the page fixes shipped; record the pending P3 browser
  verification of the chart icon (blocked on the Chrome extension); cross-
  reference the new userpref service as the home for per-user alert prefs.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant