feat(haproxy): filter dashboard tiles by agent haproxy capability - #115
Merged
Conversation
The HAProxy dashboard at /haproxy iterates every enabled server and
attaches `hx-get="/htmx/{box}/stats"` + `/metrics` to each row. On a
box whose agent has no haproxy gear (e.g. container-mode agent on
TrueNAS — see issue #112), those polls return 503 forever and the
tiles stay empty. Light-hugger's deployment exhibits this whenever
mjolnir is in the box roster.
Plumb the framework Handler's CapabilitiesCache through into the
ServerAdapter (wired in main.go via the new SetCapabilitiesCache /
CapabilitiesCache pair) and add GetEnabledServersWithGearAvailable on
the adapter. The HAProxy gear's overview + status-grid handlers now
filter through that, so boxes whose probe table marks haproxy as
unavailable just don't appear on the page.
Fail-open contract: an agent whose capabilities can't be fetched
(unreachable, no API key, TLS verify off) is still included, matching
how filterGearsByAgentCapabilities handles the same case. A transient
agent outage doesn't make the page go dark.
Default landing route per box (the other half of slice 3 in the
roadmap) is left for a follow-up — this PR is scoped to the tile-
filtering symptom.
Phase 2 slice of #112.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR updates the HAProxy dashboard to avoid rendering (and continuously polling) HAProxy tiles for boxes whose agents report that the haproxy gear is unavailable, using the dashboard’s existing agent capability probe-table cache as the source of truth.
Changes:
- Plumbs the handler’s shared
CapabilitiesCacheintoServerAdapterand addsGetEnabledServersWithGearAvailable(gearName)for capability-aware box filtering. - Updates HAProxy overview + status-grid handlers to use the new adapter method to filter out non-HAProxy-capable boxes (fail-open when capabilities can’t be fetched).
- Exposes
Handler.CapabilitiesCache()and wires it incmd/server/main.goduring app startup.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
gearbox/internal/gears/haproxy/handlers.go |
Filters HAProxy pages to boxes whose agents report haproxy available. |
gearbox/internal/framework/services/server_adapter.go |
Adds optional capabilities cache + filtering method for enabled servers by agent gear availability. |
gearbox/internal/framework/handler/handler.go |
Exposes the handler’s shared capabilities cache via an accessor. |
gearbox/cmd/server/main.go |
Wires the handler capabilities cache into the server adapter for use by gear plugins. |
Comments suppressed due to low confidence (1)
gearbox/internal/gears/haproxy/handlers.go:36
OverviewPagenow bases the “no servers configured” redirect on the filtered HAProxy-capable list. If boxes are configured but none advertises the haproxy capability, this will redirect to/settings/boxes(which is misleading) and makes it impossible to show a “no HAProxy-capable boxes” empty state. Consider distinguishing between (a) zero enabled boxes vs (b) zero HAProxy-capable boxes, and render an explicit empty-state for (b) (or redirect to a more relevant settings page).
servers := h.getHAProxyServers()
// If no servers configured, redirect to servers settings page
if len(servers) == 0 {
http.Redirect(w, r, "/settings/boxes", http.StatusSeeOther)
return
4 tasks
…ear list (#112) Two Copilot review findings on the original capability-aware filter: 1. Doc inaccuracy: the example agent-gear list included "services", but the agent has no `services` gear (the /api/v1/services endpoint is served by the `metrics` gear). Replace with the correct list and call out that some dashboard gears (services, alerts, bx) don't have agent counterparts and shouldn't be filtered via this helper. 2. O(N * fetchTimeout) cold-cache latency: synchronous fetches could stall a page render to N × fetchTimeout when multiple agents are unreachable. Parallelize across boxes with one goroutine per box and a sync.WaitGroup join, so cold-cache renders pay one round of timeout at most. Warm-cache renders hit the cache's read-lock and stay sub-millisecond. Box order is preserved by writing into a positional `keep []bool` and compacting after the wait. `go test -race` clean on services/handler/agent/haproxy packages. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
…oxy boxes (#112) (#118) Previously, OverviewPage redirected to /settings/boxes whenever its server list was empty. After the haproxy-capability filter from the parent commit, that list can be empty for two distinct reasons: 1. No enabled boxes at all — operator hasn't configured anything. 2. Enabled boxes exist, but none advertise the haproxy gear (e.g. a TrueNAS host running gearbox-agent in a container without HAProxy installed). Redirecting on case 2 is hostile UX: the operator is on /haproxy on purpose and gets bounced away with no explanation. Render an inline InfoAlert instead, pointing them at the Bx fleet view and the boxes settings page so they can either fix the agent or add a different host. Touches the Overview templ to accept an optional emptyReason string; existing call site is the only one and is updated in this commit. StatusGridPage gets the same redirect-vs-render treatment (its empty-state markup is the template's own concern). Phase 2 slice of #112. Stacked on fix/issue-112-haproxy-tile-capabilities (PR #115). Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Comment on lines
+82
to
+90
| func (h *Handlers) getServerLists() (all, haproxyCapable []models.BoxConfig) { | ||
| serverAdapter, ok := h.deps.Servers.(*services.ServerAdapter) | ||
| if !ok { | ||
| h.deps.Logger.Error("failed to get server adapter - unexpected type") | ||
| return nil | ||
| return nil, nil | ||
| } | ||
| return serverAdapter.GetEnabledServersAsModels() | ||
| all = serverAdapter.GetEnabledServersAsModels() | ||
| haproxyCapable = serverAdapter.GetEnabledServersWithGearAvailable("haproxy") | ||
| return all, haproxyCapable |
5 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
/haproxy) attacheshx-get="/htmx/{box}/stats"+/metricsto every enabled server. On a box whose agent has no haproxy gear (e.g. container-mode agent on TrueNAS — issue Capability-driven autoconfig: every gear page must honor the active box's actual capabilities (umbrella) #112), those polls return503 No stats availableforever and the tiles stay empty. Light-hugger's deployment exhibits this whenever Mjolnir is in the box roster.Handler.CapabilitiesCachethrough into theServerAdapterand exposeGetEnabledServersWithGearAvailable(gearName). The HAProxy gear's overview + status-grid handlers now filter through it, so boxes whose probe table marks haproxy as unavailable simply don't appear on the page.filterGearsByAgentCapabilitieshandles the same case. A transient agent outage doesn't make the page go dark.Phase 2 slice of #112.
Test plan
go build ./...cleango vet ./...cleango test -count=1 ./internal/framework/services/... ./internal/framework/handler/... ./internal/framework/agent/... ./internal/gears/haproxy/...clean/haproxyand the 503 burst on/htmx/{nonHAProxy}/statsis gone.🤖 Generated with Claude Code