feat(logs): derive default log sources from agent capabilities - #116
Merged
Conversation
APILogSourcesHandler returned a hardcoded [haproxy, system] pair whenever no explicit per-box settings existed. On a box whose agent has no `logs` (journalctl) or `access-log` gear — e.g. mjolnir's distroless container agent — the Logs page would still load both defaults and then trip every fetch against the agent, which has nothing to serve. Combined with the plain-text 5xx error responses (see #112 / sibling PR #114) this produced the user-visible "Error loading logs: Unexpected token 'F'" toast. Drive the no-settings default off the agent's probe table: * `haproxy` is offered when either access-log OR logs gear is available (access-log streams directly from /var/log/haproxy; the logs gear can serve it via journalctl/file too). * `system` is offered only when the `logs` gear is available (it needs journalctl/tail). * If the agent has neither, return an empty source list — the JS renders an empty dropdown and skips the immediate fetch instead of 5xx-storming. * If capabilities aren't reachable at all (agent down, older agent that pre-dates probing) fall back to the legacy pair so existing deployments still work through transient outages. 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 dashboard’s Logs API to derive the default log-source dropdown entries from the selected box’s agent capability/probe table, instead of always defaulting to the legacy haproxy + system pair when no per-box settings exist. This supports the broader “capability-driven autoconfig” effort so the Logs page doesn’t immediately issue requests for log gears the agent cannot serve.
Changes:
- Replace the hardcoded no-settings default in
APILogSourcesHandlerwith a capability-driven default resolver. - Add
defaultLogSourcesForBox, which selectshaproxyand/orsystembased on the agent’saccess-log/logsgear availability, with a fail-open fallback when capabilities cannot be fetched.
Comments suppressed due to low confidence (1)
gearbox/internal/framework/handler/api_logs.go:140
- Returning an empty sources list here assumes the frontend will preserve the empty dropdown and skip the initial log fetch, but the current Logs page JS treats
sources.length === 0as a failure and falls back to the legacy defaults, which would reintroduce the bad fetches on boxes without log gears. Either adjust the API contract so callers can distinguish "no sources available" from "error" (and update the JS accordingly), or avoid returning an empty list until the frontend handling is in place.
if !hasAccessLog && !hasLogs {
// Agent surfaces a probe table but neither log gear is available.
// Returning the empty list lets the JS render an empty dropdown
// and skip the immediate fetch — better than 5xx storming.
return []map[string]string{}
…ity (#112) Copilot pointed out that defaultLogSourcesForBox conflated two distinct cases: 1. The agent doesn't surface a gear name at all (older agent that pre-dates the gear). 2. The agent reports the gear as unavailable. Both used IsAvailable() and collapsed into "return empty list", which violates the function comment's fail-open promise: a forward-compatibility gap would silently strip the source list. Use caps.Has() to distinguish presence from availability: - If the agent surfaces neither access-log nor logs by name → fail open to the legacy [haproxy, system] pair (older-agent path). - If the agent surfaces at least one and reports both unavailable → return empty (the only case where the JS shows an empty dropdown). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
7 tasks
sarg3nt
added a commit
that referenced
this pull request
May 17, 2026
Two findings: 1. Services dashboard gear had no entry in dashboardGearToAgentGear, so the sidebar filter passed it through unconditionally — the exact symptom this PR was supposed to fix. Map it to "metrics" (the agent gear that registers /api/v1/services per gearbox-agent/internal/gears/metrics/plugin.go), and document that this is imprecise: the agent's metrics gear can be Available on a host where systemd isn't introspectable from the container, so a tighter gate (an explicit `services` capability that advertises systemd reachability) is part of the Phase 2 extension in #112. 2. No regression test covered the middleware path or the filter's fail-open contract. Add three focused tests on filterGearsByAgentCapabilities driven by a real httptest.Server returning a CapabilitiesResponse: - Mjolnir-shaped probe table → only available gears + dashboard- only gears (alerts) survive. - Agent unreachable → fail open, full list passes through. - Older agent that doesn't surface a gear name at all → that gear is kept (forward-compat, mirrors the #116 fix). The tests share a slim Handler fixture (logger, capabilities cache, static servers slice) so they exercise the production fetch path through CapabilitiesCache without needing a DB or auth manager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
sarg3nt
added a commit
that referenced
this pull request
May 17, 2026
* feat(sidebar): hide gears whose agent reports them unavailable (#112) filterGearsByAgentCapabilities is the framework's existing capability-aware gear filter. Today it's only used by the Gears settings page (`/settings/gears`) — so the **sidebar** still renders HAProxy, Logs, Services, Certificates, etc. for an active box whose agent doesn't run any of those gears. Clicking them lands the operator on an empty page or a JSON-envelope error. Run the same filter on the per-box integration list inside InjectIntegrationStatus, so the sidebar reflects what the agent can actually serve. Failures stay fail-open (an unreachable agent does not strip the sidebar) by reusing the existing helper's contract. Phase 2 slice of #112. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix(sidebar): address Copilot review on PR 119 (#112) Two findings: 1. Services dashboard gear had no entry in dashboardGearToAgentGear, so the sidebar filter passed it through unconditionally — the exact symptom this PR was supposed to fix. Map it to "metrics" (the agent gear that registers /api/v1/services per gearbox-agent/internal/gears/metrics/plugin.go), and document that this is imprecise: the agent's metrics gear can be Available on a host where systemd isn't introspectable from the container, so a tighter gate (an explicit `services` capability that advertises systemd reachability) is part of the Phase 2 extension in #112. 2. No regression test covered the middleware path or the filter's fail-open contract. Add three focused tests on filterGearsByAgentCapabilities driven by a real httptest.Server returning a CapabilitiesResponse: - Mjolnir-shaped probe table → only available gears + dashboard- only gears (alerts) survive. - Agent unreachable → fail open, full list passes through. - Older agent that doesn't surface a gear name at all → that gear is kept (forward-compat, mirrors the #116 fix). The tests share a slim Handler fixture (logger, capabilities cache, static servers slice) so they exercise the production fetch path through CapabilitiesCache without needing a DB or auth manager. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4 tasks
sarg3nt
added a commit
that referenced
this pull request
May 17, 2026
The agent-side companion PR adds a structured Resources field to
ProbeResult and has the access-log gear publish a `log_sources` list
(one entry per discovered web-server access log, name + display_name
+ path). This commit teaches the dashboard to read it.
- agent.CapabilityEntry mirrors the new `resources` JSON field as
map[string]any so per-gear shapes don't need a shared Go struct.
- BoxCapabilities.Resource(gearName, key) — small helper to look up
a single resource without manually chaining Entry/.Resources/map
indexing.
- defaultLogSourcesForBox prefers the agent's published log_sources
when present, falling back to the existing capability heuristic
for pre-Phase-2 agents and the legacy [haproxy, system] pair when
capabilities aren't reachable. The path field the agent publishes
is dropped at this layer — the dashboard doesn't expose log paths
to the browser.
The resolution chain is now (in priority):
1. Operator's saved per-box log-source settings (DB)
2. Agent's structured log_sources resource (Phase 2)
3. Capability heuristic (pre-Phase-2 fallback)
4. Legacy [haproxy, system] (agent unreachable / forward-compat)
Tests cover the new path: JSON-decoded shape (production), Go-typed
shape (in-process tests), missing access-log gear, missing resource
key, and five malformed-payload variants (string instead of array,
array of strings, missing fields, empty array). All exercise
logSourcesFromResources directly so the helper's contract is locked
in independently of the surrounding handler.
Phase 2 of #112. Stacked on fix/issue-112-log-sources-capabilities
(PR #116); pairs with feature/issue-112-probe-resources (PR #135)
on the agent.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
APILogSourcesHandlerreturned a hardcoded[haproxy, system]pair whenever no per-box settings existed. On a box whose agent has nologsoraccess-loggear (e.g. Mjolnir's distroless container agent), the Logs page would load both defaults and then fail every fetch against the agent that has nothing to serve. Combined with the plain-text 5xx responses (fixed in sibling PR fix(errors): emit JSON envelope from WriteHTTPError + harden fetch callers #114), this produced the visibleError loading logs: Unexpected token 'F'toast.haproxyoffered when eitheraccess-logorlogsgear is availablesystemoffered only whenlogsgear is available[haproxy, system]so deployments work through transient outagesapi_logs.go::APILogsHandler, services page, logs page) continue to work — the resolver is internal.Phase 2 slice of #112.
Test plan
go build ./...andgo vet ./...cleango test -count=1 ./internal/framework/handler/...cleanaccess-log/host/metricsavailable), navigate to/logs?server=mjolnirand confirm the source dropdown shows onlyHAProxy(noSystem) and the page loads access-log content cleanly.logsavailable), confirm bothHAProxyandSystemappear in the dropdown — backwards compatible./logs— confirm the legacy fallback kicks in and the page degrades gracefully.🤖 Generated with Claude Code