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:
2026-07-10 16:03:32 +02:00
parent 0cc70a7a5a
commit c084046af0
15 changed files with 1180 additions and 44 deletions
+19
View File
@@ -260,6 +260,8 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
var eventCounts map[string]int
var appTelemetry []store.CustomerAppSummary
var logTails []store.AppLogTail
var pendingTails []string
if customer != nil {
history, _ = s.store.GetCustomerHistory(customerID, 24*time.Hour)
@@ -268,6 +270,8 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
events, _ = s.store.GetRecentEvents(customerID, 50)
eventCounts, _ = s.store.CountEventsBySeverity(customerID, time.Now().Add(-24*time.Hour))
appTelemetry, _ = s.store.GetCustomerAppSummary(customerID, time.Now().Add(-7*24*time.Hour))
logTails, _ = s.store.GetCustomerLogTails(customerID)
pendingTails, _ = s.store.GetPendingLogTailRequests(customerID)
}
type pageData struct {
@@ -306,6 +310,12 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
AppTelemetry []store.CustomerAppSummary
HasAppTelemetry bool
// On-demand log tails (v0.43.0): received tails (last 2 per app) + apps with a
// still-pending request (badge on the telemetry row).
LogTails []store.AppLogTail
HasLogTails bool
PendingTails map[string]bool
HasDRRecipe bool
DRRecipeUpdatedAt string
DRRecipeHasHost bool
@@ -320,6 +330,11 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
ScriptVersion string
}
pendingSet := make(map[string]bool, len(pendingTails))
for _, app := range pendingTails {
pendingSet[app] = true
}
// DR recipe presence — show the secret-free reconstruction recipe panel + download link when
// either half has landed (host-report and/or controller report).
var hasDR, drHost, drApps bool
@@ -366,6 +381,10 @@ func (s *Server) handleCustomerUnified(w http.ResponseWriter, r *http.Request, c
AppTelemetry: appTelemetry,
HasAppTelemetry: len(appTelemetry) > 0,
LogTails: logTails,
HasLogTails: len(logTails) > 0,
PendingTails: pendingSet,
HasDRRecipe: hasDR,
DRRecipeUpdatedAt: drUpdated,
DRRecipeHasHost: drHost,