diff --git a/REUSE.md b/REUSE.md index aae84ec..20868d9 100644 --- a/REUSE.md +++ b/REUSE.md @@ -83,6 +83,8 @@ | `copyFile` | hub/internal/assets/assets.go (~L259) | `(src, dst) error` | THE atomic file write (tmp + rename) in the hub | Copy this shape for any new on-disk write. | | `fileSHA256` | hub/internal/assets/assets.go (~L244) | `(path) (string, error)` | Streaming sha256 of a file | — | | `(*Store).SaveEvent` | hub/internal/store/store.go (~L1003) | `(...) (int64, error)` | Persisting ANY event (controller or hub source) | Pair with dispatcher/`onEvent` — saving alone never notifies. | +| `(*Store).RequestLogTail` / `GetPendingLogTailRequests` / `SaveAppLogTail` | hub/internal/store/logtail.go | pending-intent + consume-once fulfillment | THE ACK-flag pull pattern for hub→box requests (copy for any new one) | SaveAppLogTail clears the request in the SAME tx (consume-once) + prunes to last 2 per (customer,app); the hub NEVER connects into a box | +| `upsertAppIssue` dismissal/context semantics | hub/internal/store/telemetry.go | ON CONFLICT CASE guards | Issue dismissal + first-capture-wins context | Un-dismiss ONLY on `excluded.last_seen > dismissed_at`; context adopted only while stored one is empty — do not "simplify" either CASE (red-proofed) | | `store.GuestID` | hub/internal/store/store.go (~L1268) | `(hostID string, vmid int) string` | Canonical guest primary key | Never hand-concatenate host+vmid. | | `scheduleDaily` | hub/cmd/hub/main.go (~L449) | `(ctx, name, "HH:MM", fn, logger)` | Daily jobs in Europe/Budapest (prune etc.) | Blocking — run as goroutine. `parseHM` returns 0,0 (midnight) on bad input. | diff --git a/hub/CHANGELOG.md b/hub/CHANGELOG.md index 98ec1c6..08a0fef 100644 --- a/hub/CHANGELOG.md +++ b/hub/CHANGELOG.md @@ -1,5 +1,47 @@ # Felhom Hub — Changelog +## v0.43.0 — Remote app-log diagnostics: copyable issues + error context + on-demand log tails (2026-07-10) + +Pairs with controller v0.111.0. Motivated live: Peti's CWA NFS issue was visible in Known Issues but +tooltip-only unreadable and context-free, and there was no way to see the app's actual logs without box +access. The hub still never connects into a guest — everything rides the existing report + ACK. + +- **Part A — readable, copyable issues** (`templates/app_detail.html`): Known Issues rows are + click-to-expand — full message in a wrapping monospace `
` + **Copy** button (clipboard API with
+  execCommand fallback), fingerprint/severity/first-last-seen in the body. Tooltip-only truncation killed.
+- **Part C — context stored + rendered** (`store/telemetry.go`): `app_log_issues` gains `context`
+  (JSON array) + `context_customer` (provenance); `upsertAppIssue` stores context on INSERT and adopts a
+  later one ONLY while the stored context is empty (**first capture wins** — stable repro, no churn).
+  Rendered in the expanded row as "Context around first occurrence — from ", copyable.
+  Nil-safe with pre-v0.111 reports.
+- **Part D — on-demand ordered log tail (pull-based)**: per-app **"Request log tail"** button on the
+  customer page → `log_tail_requests` row (one active per app; re-click refreshes) + a customer-visible
+  `log_tail_requested` event (transparency by default). The report ACK advertises
+  `log_tail_requests: [app…]` (same additive omit-when-empty pattern as escrow); the controller's next
+  report ships `log_tails` → stored in `app_log_tails` (transient, **last 2 per app kept**) and the
+  request is **cleared (consume-once)**. Ordered tail view with line numbers (`log_tail.html`) +
+  **Download .log**; tail reads are customer-scoped.
+- **Part F fix — the 24h/7d/30d selector now filters Known Issues**: `GetAppIssues` gained the same
+  `since` cutoff the Memory Trend uses (it had NO time filter — a 24h view showed 25-day-old rows).
+- **Part G — deletion → dismissal**: diagnosis = the delete handler was NOT broken; deletion is futile
+  because the controller re-scans its rolling 15-minute window every report and re-upserts a still-
+  occurring fingerprint with fresh `last_seen` minutes later. Replaced with `dismissed_at`:
+  Dismiss Selected/All (buttons renamed), dismissed rows out of the default view ("Show dismissed"
+  toggle), and `upsertAppIssue` un-dismisses ONLY on `excluded.last_seen > dismissed_at` — a re-sent
+  old window stays hidden, a genuinely NEW occurrence resurfaces (recurrence never silently swallowed).
+- **Part H — per-customer scoping**: `?customer=` on the app detail page filters Known Issues to
+  rows whose `affected_customers` contains the id (header shows "filtered: "); the customer
+  page's App Telemetry rows link there (the drill-down). The fleet view stays; the expanded row lists
+  the affected customers explicitly (linked) and the count column is labeled
+  "Occurrences (all customers)".
+- **Tests + red-proofs (all four failed exactly as designed, restored green):** dismissal guard dropped
+  → old-window re-report resurrected the row → FAIL; range predicate neutered → 10d-old issue visible
+  at 24h → FAIL; first-capture-wins dropped → empty-context upsert clobbered stored context → FAIL;
+  consume-once DELETE removed → request survived fulfillment (store test + API ACK round-trip both) →
+  FAIL. Plus: late-context adoption, warn-no-context, occurrence counting, tail request/fulfill/prune-
+  to-2/cross-customer-404, ACK omit-when-empty baseline, render tests (expanded row content, customer
+  page sections, ordered tail view + download headers).
+
 ## v0.42.0 — Remote "Debug mód" toggle on the customer config editor (2026-07-10)
 
 Lets an operator flip the controller's debug mode (verbose log + the `/debug` menu, which the controller