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