hub v0.43.0: remote app-log diagnostics — copyable issues + context + on-demand log tails + range/dismissal fixes
- store: app_log_issues gains context/context_customer (first capture wins) + dismissed_at (resurface only on last_seen > dismissed_at); log_tail_requests (pending operator intents, consume-once) + app_log_tails (transient, keep last 2 per app) - api: /report ingests log_tails (stores + clears the request); ACK advertises log_tail_requests (same additive omit-when-empty pattern as escrow) - web: Known Issues rows click-to-expand (full copyable message + context with provenance + explicit affected-customers list); Dismiss replaces Delete; period selector now filters issues (F); ?customer= filtered view + customer-page drill-down links (H); per-app Request-log-tail button + pending badge + App Log Tails section + ordered tail view with line numbers + .log download; customer-visible log_tail_requested event - tests: store (context first-capture/late-adopt, range filter, dismissal old-window vs new-occurrence, tail request/fulfill/prune/scoping), api ACK round-trip, web render (expanded row, customer page sections, tail view + download + cross-customer 404) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PSK5g6qYLknKj8u3QAFEr6
This commit is contained in:
@@ -304,6 +304,29 @@ func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
// On-demand log tails (v0.43.0) — the controller ships these on the cycle after the ACK
|
||||
// requested them. Storing one clears its pending request (consume-once, in SaveAppLogTail)
|
||||
// so the next ACK stops advertising it. Backward-compatible: old controllers never send this.
|
||||
var tailPayload struct {
|
||||
LogTails []struct {
|
||||
App string `json:"app"`
|
||||
CollectedAt time.Time `json:"collected_at"`
|
||||
Lines []string `json:"lines"`
|
||||
} `json:"log_tails"`
|
||||
}
|
||||
if err := json.Unmarshal(body, &tailPayload); err == nil {
|
||||
for _, lt := range tailPayload.LogTails {
|
||||
if lt.App == "" {
|
||||
continue
|
||||
}
|
||||
if err := h.store.SaveAppLogTail(payload.CustomerID, lt.App, lt.CollectedAt, lt.Lines); err != nil {
|
||||
h.logger.Printf("[WARN] Failed to save log tail %s/%s: %v", payload.CustomerID, lt.App, err)
|
||||
} else {
|
||||
h.logger.Printf("[INFO] Log tail received for %s/%s (%d lines)", payload.CustomerID, lt.App, len(lt.Lines))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// DR recipe — persist the controller's secret-free customer/apps half (preserving any host half).
|
||||
// Backward-compatible (old controllers won't have this field); a failure must not drop the report.
|
||||
var drPayload struct {
|
||||
@@ -342,6 +365,13 @@ func (h *Handler) handleReport(w http.ResponseWriter, r *http.Request) {
|
||||
resp["escrow"] = es
|
||||
}
|
||||
|
||||
// v0.43.0 — pending log-tail requests (same additive ACK-flag pattern as escrow): the
|
||||
// controller collects the named apps' tails and ships them on its NEXT report; the field
|
||||
// is omitted when nothing is pending. The hub never connects into the box.
|
||||
if apps, err := h.store.GetPendingLogTailRequests(payload.CustomerID); err == nil && len(apps) > 0 {
|
||||
resp["log_tail_requests"] = apps
|
||||
}
|
||||
|
||||
// Phase 2 managed updates: advertise the effective controller-version FLOOR (per-customer override
|
||||
// else global default) and the latest available version. The controller compares its current
|
||||
// version against the floor and auto-updates when below it (latest stays the customer's opt-in
|
||||
|
||||
Reference in New Issue
Block a user